From 8d173637f78996dba60c8fa2100688a8cad39888 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Fri, 10 Sep 2021 13:56:25 -0400 Subject: [PATCH] Bug 505882: Fork the GnuMakefileGenerator into non-API The GnuMakefileGenerator is a huge file that needs lots of fixes. However most of the time to implement the fixes are taken up trying to not break API. Most extenders of CDT provide their own implementation of GnuMakefileGenerator by copying the CDT one and modifying it. Therefore CDT is going to take that same approach internally and leave the existing GnuMakefileGenerator in its unmodifiable state. Change-Id: I12fafb5e347112648a02d4593267bd88a47b5c88 --- .../plugin.xml | 54 +- .../core/tests/BuildFileGenerator.java | 2 +- .../core/tests/ManagedBuildCoreTests.java | 2 +- .../META-INF/MANIFEST.MF | 1 + .../plugin.xml | 2 +- .../core/ManagedBuildManager.java | 2 +- .../makegen/gnu/GnuDependencyGroupInfo.java | 10 +- .../makegen/gnu/GnuMakefileGenerator.java | 2 + .../makegen/gnu/IManagedBuildGnuToolInfo.java | 2 + .../makegen/gnu/ManagedBuildGnuToolInfo.java | 2 + .../makegen/gnu2/GnuMakefileGenerator.java | 4790 +++++++++++++++++ .../gnu2/IManagedBuildGnuToolInfo.java | 128 + .../makegen/gnu2/ManagedBuildGnuToolInfo.java | 1004 ++++ .../META-INF/MANIFEST.MF | 2 +- .../plugin.xml | 2 +- .../guide/deprecated_API_removals.html | 22 + 16 files changed, 5992 insertions(+), 35 deletions(-) create mode 100644 build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/GnuMakefileGenerator.java create mode 100644 build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/IManagedBuildGnuToolInfo.java create mode 100644 build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/ManagedBuildGnuToolInfo.java diff --git a/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml b/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml index 2d145e30ce0..51e116fb6f4 100644 --- a/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml +++ b/build/org.eclipse.cdt.managedbuilder.core.tests/plugin.xml @@ -1234,7 +1234,7 @@ name="Dbg Builder" command="make" arguments="-k" - buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> @@ -3513,7 +3513,7 @@ + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> @@ -5166,7 +5166,7 @@ name="Builder" command="make" arguments="-k" - buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> @@ -5239,7 +5239,7 @@ name="Builder" command="make" arguments="-k" - buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> @@ -5283,7 +5283,7 @@ variableFormat="@=" isVariableCaseSensitive="false" reservedMacroNames="PATH" - buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator"> + * If sub-classing and using {@link DefaultGCCDependencyCalculator3}, make sure to also override + * {@link DefaultGCCDependencyCalculator3#createMakefileGenerator()} to return the appropriate result. + * + * @since 1.2 + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class GnuMakefileGenerator implements IManagedBuilderMakefileGenerator2 { + private static final IPath DOT_SLASH_PATH = new Path("./"); //$NON-NLS-1$ + + private Pattern doubleQuotedOption = Pattern.compile("--?[a-zA-Z]+.*?\\\".*?\\\".*"); //$NON-NLS-1$ + private Pattern singleQuotedOption = Pattern.compile("--?[a-zA-Z]+.*?'.*?'.*"); //$NON-NLS-1$ + + /** + * This class walks the delta supplied by the build system to determine + * what resources have been changed. The logic is very simple. If a + * buildable resource (non-header) has been added or removed, the directories + * in which they are located are "dirty" so the makefile fragments for them + * have to be regenerated. + *

+ * The actual dependencies are recalculated as a result of the build step + * itself. We are relying on make to do the right things when confronted + * with a dependency on a moved header file. That said, make will treat + * the missing header file in a dependency rule as a target it has to build + * unless told otherwise. These dummy targets are added to the makefile + * to avoid a missing target error. + */ + public class ResourceDeltaVisitor implements IResourceDeltaVisitor { + private final GnuMakefileGenerator generator; + // private IManagedBuildInfo info; + private final IConfiguration config; + + /** + * The constructor + */ + public ResourceDeltaVisitor(GnuMakefileGenerator generator, IManagedBuildInfo info) { + this.generator = generator; + this.config = info.getDefaultConfiguration(); + } + + public ResourceDeltaVisitor(GnuMakefileGenerator generator, IConfiguration cfg) { + this.generator = generator; + this.config = cfg; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) + */ + @Override + public boolean visit(IResourceDelta delta) throws CoreException { + // Should the visitor keep iterating in current directory + boolean keepLooking = false; + IResource resource = delta.getResource(); + IResourceInfo rcInfo = config.getResourceInfo(resource.getProjectRelativePath(), false); + IFolderInfo fo = null; + boolean isSource = isSource(resource.getProjectRelativePath()); + if (rcInfo instanceof IFolderInfo) { + fo = (IFolderInfo) rcInfo; + } + // What kind of resource change has occurred + if (/*!rcInfo.isExcluded() && */isSource) { + if (resource.getType() == IResource.FILE) { + String ext = resource.getFileExtension(); + switch (delta.getKind()) { + case IResourceDelta.ADDED: + if (!generator.isGeneratedResource(resource)) { + // This is a source file so just add its container + if (fo == null || fo.buildsFileType(ext)) { + generator.appendModifiedSubdirectory(resource); + } + } + break; + case IResourceDelta.REMOVED: + // we get this notification if a resource is moved too + if (!generator.isGeneratedResource(resource)) { + // This is a source file so just add its container + if (fo == null || fo.buildsFileType(ext)) { + generator.appendDeletedFile(resource); + generator.appendModifiedSubdirectory(resource); + } + } + break; + default: + keepLooking = true; + break; + } + } + + if (resource.getType() == IResource.FOLDER) { + // I only care about delete event + switch (delta.getKind()) { + case IResourceDelta.REMOVED: + if (!generator.isGeneratedResource(resource)) { + generator.appendDeletedSubdirectory((IContainer) resource); + } + 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 (resource.getType() == IResource.ROOT || (isSource && !generator.isGeneratedResource(resource))) { + keepLooking = true; + } + } + + return keepLooking; + } + } + + /** + * This class is used to recursively walk the project and determine which + * modules contribute buildable source files. + */ + protected class ResourceProxyVisitor implements IResourceProxyVisitor { + private final GnuMakefileGenerator generator; + private final IConfiguration config; + // private IManagedBuildInfo info; + + /** + * Constructs a new resource proxy visitor to quickly visit project + * resources. + */ + public ResourceProxyVisitor(GnuMakefileGenerator generator, IManagedBuildInfo info) { + this.generator = generator; + this.config = info.getDefaultConfiguration(); + } + + public ResourceProxyVisitor(GnuMakefileGenerator generator, IConfiguration cfg) { + this.generator = generator; + this.config = cfg; + } + + /* (non-Javadoc) + * @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy) + */ + @Override + public boolean visit(IResourceProxy proxy) throws CoreException { + // No point in proceeding, is there + if (generator == null) { + return false; + } + + IResource resource = proxy.requestResource(); + boolean isSource = isSource(resource.getProjectRelativePath()); + + // Is this a resource we should even consider + if (proxy.getType() == IResource.FILE) { + // If this resource has a Resource Configuration and is not excluded or + // if it has a file extension that one of the tools builds, add the sudirectory to the list + // boolean willBuild = false; + IResourceInfo rcInfo = config.getResourceInfo(resource.getProjectRelativePath(), false); + if (isSource/* && !rcInfo.isExcluded()*/) { + boolean willBuild = false; + if (rcInfo instanceof IFolderInfo) { + String ext = resource.getFileExtension(); + if (((IFolderInfo) rcInfo).buildsFileType(ext) && + // If this file resource is a generated resource, then it is uninteresting + !generator.isGeneratedResource(resource)) { + willBuild = true; + } + } else { + willBuild = true; + } + + if (willBuild) + generator.appendBuildSubdirectory(resource); + } + // if (willBuild) { + // if ((resConfig == null) || (!(resConfig.isExcluded()))) { + // generator.appendBuildSubdirectory(resource); + // } + // } + return false; + } else if (proxy.getType() == IResource.FOLDER) { + + if (!isSource || generator.isGeneratedResource(resource)) + return false; + return true; + } + + // Recurse into subdirectories + return true; + } + + } + + // String constants for makefile contents and messages + private static final String COMMENT = "MakefileGenerator.comment"; //$NON-NLS-1$ + //private static final String AUTO_DEP = COMMENT + ".autodeps"; //$NON-NLS-1$ + //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 DEP_INCL = COMMENT + ".module.dep.includes"; //$NON-NLS-1$ + private static final String HEADER = COMMENT + ".header"; //$NON-NLS-1$ + + protected static final String MESSAGE_FINISH_BUILD = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.finish.build"); //$NON-NLS-1$ + protected static final String MESSAGE_FINISH_FILE = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.finish.file"); //$NON-NLS-1$ + protected static final String MESSAGE_START_BUILD = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.start.build"); //$NON-NLS-1$ + protected static final String MESSAGE_START_FILE = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.start.file"); //$NON-NLS-1$ + protected static final String MESSAGE_START_DEPENDENCY = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.start.dependency"); //$NON-NLS-1$ + protected static final String MESSAGE_NO_TARGET_TOOL = ManagedMakeMessages + .getResourceString("MakefileGenerator.message.no.target"); //$NON-NLS-1$ + //private static final String MOD_INCL = COMMENT + ".module.make.includes"; //$NON-NLS-1$ + private static final String MOD_LIST = COMMENT + ".module.list"; //$NON-NLS-1$ + private static final String MOD_VARS = COMMENT + ".module.variables"; //$NON-NLS-1$ + private static final String MOD_RULES = COMMENT + ".build.rule"; //$NON-NLS-1$ + private static final String BUILD_TOP = COMMENT + ".build.toprules"; //$NON-NLS-1$ + private static final String ALL_TARGET = COMMENT + ".build.alltarget"; //$NON-NLS-1$ + private static final String MAINBUILD_TARGET = COMMENT + ".build.mainbuildtarget"; //$NON-NLS-1$ + private static final String BUILD_TARGETS = COMMENT + ".build.toptargets"; //$NON-NLS-1$ + private static final String SRC_LISTS = COMMENT + ".source.list"; //$NON-NLS-1$ + + private static final String EMPTY_STRING = ""; //$NON-NLS-1$ + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private static final String OBJS_MACRO = "OBJS"; //$NON-NLS-1$ + private static final String MACRO_ADDITION_ADDPREFIX_HEADER = "${addprefix "; //$NON-NLS-1$ + private static final String MACRO_ADDITION_ADDPREFIX_SUFFIX = "," + WHITESPACE + LINEBREAK; //$NON-NLS-1$ + private static final String MACRO_ADDITION_PREFIX_SUFFIX = "+=" + WHITESPACE + LINEBREAK; //$NON-NLS-1$ + private static final String PREBUILD = "pre-build"; //$NON-NLS-1$ + private static final String MAINBUILD = "main-build"; //$NON-NLS-1$ + private static final String POSTBUILD = "post-build"; //$NON-NLS-1$ + private static final String SECONDARY_OUTPUTS = "secondary-outputs"; //$NON-NLS-1$ + + // Enumerations + public static final int PROJECT_RELATIVE = 1, PROJECT_SUBDIR_RELATIVE = 2, ABSOLUTE = 3; + + class ToolInfoHolder { + ITool[] buildTools; + boolean[] buildToolsUsed; + ManagedBuildGnuToolInfo[] gnuToolInfos; + Set outputExtensionsSet; + List dependencyMakefiles; + } + + // Local variables needed by generator + private String buildTargetName; + private String buildTargetExt; + private IConfiguration config; + private IBuilder builder; + // private ITool[] buildTools; + // private boolean[] buildToolsUsed; + // private ManagedBuildGnuToolInfo[] gnuToolInfos; + private PathSettingsContainer toolInfos; + private Vector deletedFileList; + private Vector deletedDirList; + // private IManagedBuildInfo info; + // private IConfiguration cfg + private Vector invalidDirList; + /** Collection of Folders in which sources files have been modified */ + private Collection modifiedList; + private IProgressMonitor monitor; + private IProject project; + private IResource[] projectResources; + private Vector ruleList; + private Vector depLineList; // String's of additional dependency lines + private Vector depRuleList; // String's of rules for generating dependency files + /** Collection of Containers which contribute source files to the build */ + private Collection subdirList; + private IPath topBuildDir; // Build directory - relative to the workspace + // private Set outputExtensionsSet; + //=== Maps of macro names (String) to values (List) + // Map of source file build variable names to a List of source file Path's + private final HashMap> buildSrcVars = new HashMap<>(); + // Map of output file build variable names to a List of output file Path's + private final HashMap> buildOutVars = new HashMap<>(); + // Map of dependency file build variable names to a List of GnuDependencyGroupInfo objects + private final HashMap buildDepVars = new HashMap<>(); + private final LinkedHashMap topBuildOutVars = new LinkedHashMap<>(); + // Dependency file variables + // private Vector dependencyMakefiles; // IPath's - relative to the top build directory or absolute + + private ICSourceEntry srcEntries[]; + + public GnuMakefileGenerator() { + super(); + } + + /************************************************************************* + * IManagedBuilderMakefileGenerator M E T H O D S + ************************************************************************/ + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#initialize(IProject, IManagedBuildInfo, IProgressMonitor) + */ + @Override + public void initialize(IProject project, IManagedBuildInfo info, IProgressMonitor monitor) { + // Save the project so we can get path and member information + this.project = project; + try { + projectResources = project.members(); + } catch (CoreException e) { + projectResources = null; + } + // 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 + buildTargetName = info.getBuildArtifactName(); + // Get its extension + buildTargetExt = info.getBuildArtifactExtension(); + + try { + //try to resolve the build macros in the target extension + buildTargetExt = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(buildTargetExt, + "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, info.getDefaultConfiguration()); + } catch (BuildMacroException e) { + } + + try { + //try to resolve the build macros in the target name + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(buildTargetName, + "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, info.getDefaultConfiguration()); + if (resolved != null && (resolved = resolved.trim()).length() > 0) + buildTargetName = resolved; + } catch (BuildMacroException e) { + } + + if (buildTargetExt == null) { + buildTargetExt = ""; //$NON-NLS-1$ + } + // Cache the build tools + config = info.getDefaultConfiguration(); + builder = config.getEditableBuilder(); + initToolInfos(); + //set the top build dir path + initializeTopBuildDir(info.getConfigurationName()); + } + + /** + * This method calls the dependency postprocessors defined for the tool chain + */ + private void callDependencyPostProcessors(IResourceInfo rcInfo, ToolInfoHolder h, IFile depFile, + IManagedDependencyGenerator2[] postProcessors, // This array is the same size as the buildTools array and has + // an entry set when the corresponding tool has a dependency calculator + boolean callPopulateDummyTargets, boolean force) throws CoreException { + try { + // IPath path = depFile.getFullPath(); + // path = inFullPathFromOutFullPath(path); + // IResourceInfo rcInfo = config.getResourceInfo(path, false); + // IFolderInfo fo; + // if(rcInfo instanceof IFileInfo){ + // fo = (IFolderInfo)config.getResourceInfo(path.removeLastSegments(1), false); + // } else { + // fo = (IFolderInfo)rcInfo; + // } + // ToolInfoHolder h = getToolInfo(fo.getPath()); + updateMonitor(ManagedMakeMessages.getFormattedString("GnuMakefileGenerator.message.postproc.dep.file", //$NON-NLS-1$ + depFile.getName())); + if (postProcessors != null) { + IPath absolutePath = new Path( + EFSExtensionManager.getDefault().getPathFromURI(depFile.getLocationURI())); + // Convert to build directory relative + IPath depPath = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), absolutePath); + for (int i = 0; i < postProcessors.length; i++) { + IManagedDependencyGenerator2 depGen = postProcessors[i]; + if (depGen != null) { + depGen.postProcessDependencyFile(depPath, config, h.buildTools[i], getTopBuildDir()); + } + } + } + if (callPopulateDummyTargets) { + populateDummyTargets(rcInfo, depFile, force); + } + } catch (CoreException e) { + throw e; + } catch (IOException e) { + } + } + + /** + * This method collects the dependency postprocessors and file extensions defined for the tool chain + */ + private boolean collectDependencyGeneratorInformation(ToolInfoHolder h, Vector depExts, // Vector of dependency file extensions + IManagedDependencyGenerator2[] postProcessors) { + + boolean callPopulateDummyTargets = false; + for (int i = 0; i < h.buildTools.length; i++) { + ITool tool = h.buildTools[i]; + IManagedDependencyGeneratorType depType = tool + .getDependencyGeneratorForExtension(tool.getDefaultInputExtension()); + if (depType != null) { + int calcType = depType.getCalculatorType(); + if (calcType <= IManagedDependencyGeneratorType.TYPE_OLD_TYPE_LIMIT) { + if (calcType == IManagedDependencyGeneratorType.TYPE_COMMAND) { + callPopulateDummyTargets = true; + depExts.add(DEP_EXT); + } + } else { + if (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS + || calcType == IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS) { + IManagedDependencyGenerator2 depGen = (IManagedDependencyGenerator2) depType; + String depExt = depGen.getDependencyFileExtension(config, tool); + if (depExt != null) { + postProcessors[i] = depGen; + depExts.add(depExt); + } + } + } + } + } + return callPopulateDummyTargets; + } + + protected boolean isSource(IPath path) { + return !CDataUtil.isExcluded(path, srcEntries); + // path = path.makeRelative(); + // for(int i = 0; i < srcPaths.length; i++){ + // if(srcPaths[i].isPrefixOf(path)) + // return true; + // } + // return false; + } + + private class DepInfo { + Vector depExts; + IManagedDependencyGenerator2[] postProcessors; + boolean callPopulateDummyTargets; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#generateDependencies() + */ + @Override + public void generateDependencies() throws CoreException { + final PathSettingsContainer postProcs = PathSettingsContainer.createRootContainer(); + // Note: PopulateDummyTargets is a hack for the pre-3.x GCC compilers + + // Collect the methods that will need to be called + toolInfos.accept(new IPathSettingsContainerVisitor() { + @Override + public boolean visit(PathSettingsContainer container) { + ToolInfoHolder h = (ToolInfoHolder) container.getValue(); + Vector depExts = new Vector<>(); // Vector of dependency file extensions + IManagedDependencyGenerator2[] postProcessors = new IManagedDependencyGenerator2[h.buildTools.length]; + boolean callPopulateDummyTargets = collectDependencyGeneratorInformation(h, depExts, postProcessors); + + // Is there anyone to call if we do find dependency files? + if (!callPopulateDummyTargets) { + int i; + for (i = 0; i < postProcessors.length; i++) { + if (postProcessors[i] != null) + break; + } + if (i == postProcessors.length) + return true; + } + + PathSettingsContainer child = postProcs.getChildContainer(container.getPath(), true, true); + DepInfo di = new DepInfo(); + di.depExts = depExts; + di.postProcessors = postProcessors; + di.callPopulateDummyTargets = callPopulateDummyTargets; + child.setValue(di); + + return true; + } + }); + + IWorkspaceRoot root = CCorePlugin.getWorkspace().getRoot(); + for (IResource res : getSubdirList()) { + // The builder creates a subdir with same name as source in the build location + IContainer subDir = (IContainer) res; + IPath projectRelativePath = subDir.getProjectRelativePath(); + IResourceInfo rcInfo = config.getResourceInfo(projectRelativePath, false); + PathSettingsContainer cr = postProcs.getChildContainer(rcInfo.getPath(), false, true); + if (cr == null || cr.getValue() == null) + continue; + + DepInfo di = (DepInfo) cr.getValue(); + + ToolInfoHolder h = getToolInfo(projectRelativePath); + IPath buildRelativePath = topBuildDir.append(projectRelativePath); + IFolder buildFolder = root.getFolder(buildRelativePath); + if (buildFolder == null) + continue; + + // Find all of the dep files in the generated subdirectories + IResource[] files = buildFolder.members(); + for (IResource file : files) { + String fileExt = file.getFileExtension(); + for (String ext : di.depExts) { + if (ext.equals(fileExt)) { + IFile depFile = root.getFile(file.getFullPath()); + if (depFile == null) + continue; + callDependencyPostProcessors(rcInfo, h, depFile, di.postProcessors, di.callPopulateDummyTargets, + false); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#generateMakefiles(org.eclipse.core.resources.IResourceDelta) + */ + @Override + public MultiStatus 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(computeTopBuildDir(config.getName())); + if (!folder.exists()) { + return regenerateMakefiles(); + } + + // Return value + MultiStatus status; + + // Visit the resources in the delta and compile a list of subdirectories to regenerate + updateMonitor( + ManagedMakeMessages.getFormattedString("MakefileGenerator.message.calc.delta", project.getName())); //$NON-NLS-1$ + ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(this, config); + delta.accept(visitor); + checkCancel(); + + // Get all the subdirectories participating in the build + updateMonitor( + ManagedMakeMessages.getFormattedString("MakefileGenerator.message.finding.sources", project.getName())); //$NON-NLS-1$ + ResourceProxyVisitor resourceVisitor = new ResourceProxyVisitor(this, config); + project.accept(resourceVisitor, IResource.NONE); + checkCancel(); + + // Bug 303953: Ensure that if all resources have been removed from a folder, than the folder still + // appears in the subdir list so it's subdir.mk is correctly regenerated + getSubdirList().addAll(getModifiedList()); + + // Make sure there is something to build + if (getSubdirList().isEmpty()) { + String info = ManagedMakeMessages.getFormattedString("MakefileGenerator.warning.no.source", //$NON-NLS-1$ + project.getName()); + updateMonitor(info); + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.INFO, "", //$NON-NLS-1$ + null); + status.add(new Status(IStatus.INFO, ManagedBuilderCorePlugin.getUniqueIdentifier(), NO_SOURCE_FOLDERS, info, + null)); + return status; + } + + // Make sure the build directory is available + ensureTopBuildDir(); + checkCancel(); + + // Make sure that there is a makefile containing all the folders participating + IPath srcsFilePath = topBuildDir.append(SRCSFILE_NAME); + IFile srcsFileHandle = createFile(srcsFilePath); + buildSrcVars.clear(); + buildOutVars.clear(); + buildDepVars.clear(); + topBuildOutVars.clear(); + populateSourcesMakefile(srcsFileHandle); + checkCancel(); + + // Regenerate any fragments that are missing for the exisiting directories NOT modified + for (IResource res : getSubdirList()) { + IContainer subdirectory = (IContainer) res; + if (!getModifiedList().contains(subdirectory)) { + // Make sure the directory exists (it may have been deleted) + if (!subdirectory.exists()) { + appendDeletedSubdirectory(subdirectory); + continue; + } + // Make sure a fragment makefile exists + IPath fragmentPath = getBuildWorkingDir().append(subdirectory.getProjectRelativePath()) + .append(MODFILE_NAME); + IFile makeFragment = project.getFile(fragmentPath); + if (!makeFragment.exists()) { + // If one or both are missing, then add it to the list to be generated + getModifiedList().add(subdirectory); + } + } + } + + // Delete the old dependency files for any deleted resources + for (IResource deletedFile : getDeletedFileList()) { + deleteDepFile(deletedFile); + deleteBuildTarget(deletedFile); + } + + // Regenerate any fragments for modified directories + for (IResource res : getModifiedList()) { + IContainer subDir = (IContainer) res; + // Make sure the directory exists (it may have been deleted) + if (!subDir.exists()) { + appendDeletedSubdirectory(subDir); + continue; + } + //populateFragmentMakefile(subDir); // See below + checkCancel(); + } + + // Recreate all module makefiles + // NOTE WELL: For now, always recreate all of the fragment makefile. This is necessary + // in order to re-populate the buildVariable lists. In the future, the list could + // possibly segmented by subdir so that all fragments didn't need to be + // regenerated + for (IResource res : getSubdirList()) { + IContainer subDir = (IContainer) res; + try { + populateFragmentMakefile(subDir); + } catch (CoreException e) { + // Probably should ask user if they want to continue + checkCancel(); + continue; + } + checkCancel(); + } + + // Calculate the inputs and outputs of the Tools to be generated in the main makefile + calculateToolInputsOutputs(); + checkCancel(); + + // Re-create the top-level makefile + IPath makefilePath = topBuildDir.append(MAKEFILE_NAME); + IFile makefileHandle = createFile(makefilePath); + populateTopMakefile(makefileHandle, false); + checkCancel(); + + // Remove deleted folders from generated build directory + for (IResource res : getDeletedDirList()) { + IContainer subDir = (IContainer) res; + removeGeneratedDirectory(subDir); + checkCancel(); + } + + // How did we do + if (!getInvalidDirList().isEmpty()) { + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.WARNING, "", //$NON-NLS-1$ + null); + // Add a new status for each of the bad folders + // TODO: fix error message + for (IResource res : getInvalidDirList()) { + IContainer subDir = (IContainer) res; + status.add(new Status(IStatus.WARNING, ManagedBuilderCorePlugin.getUniqueIdentifier(), SPACES_IN_PATH, + subDir.getFullPath().toString(), null)); + } + } else { + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.OK, "", //$NON-NLS-1$ + null); + } + + return status; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#getBuildWorkingDir() + */ + @Override + public IPath getBuildWorkingDir() { + if (topBuildDir != null) { + return topBuildDir.removeFirstSegments(1); + } + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#getMakefileName() + */ + @Override + public String getMakefileName() { + return MAKEFILE_NAME; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#isGeneratedResource(org.eclipse.core.resources.IResource) + */ + @Override + public boolean isGeneratedResource(IResource resource) { + // Is this a generated directory ... + IPath path = resource.getProjectRelativePath(); + //TODO: fix to use builder output dir instead + String[] configNames = ManagedBuildManager.getBuildInfo(project).getConfigurationNames(); + for (String name : configNames) { + IPath pathOfConfig = computeTopBuildDir(name); + if (pathOfConfig.isPrefixOf(path)) { + return true; + } + } + return false; + } + + private static void save(StringBuffer buffer, IFile file) throws CoreException { + String encoding = null; + try { + encoding = file.getCharset(); + } catch (CoreException ce) { + // use no encoding + } + + byte[] bytes = null; + if (encoding != null) { + try { + bytes = buffer.toString().getBytes(encoding); + } catch (Exception e) { + } + } else { + bytes = buffer.toString().getBytes(); + } + + byte[] oldBytes = null; + try (InputStream is = file.getContents(true)) { + oldBytes = is.readAllBytes(); + } catch (IOException e) { + } + + // Only write file if content differs + if (!Arrays.equals(oldBytes, bytes)) { + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + // use a platform operation to update the resource contents + boolean force = true; + file.setContents(stream, force, false, null); // Don't record history + } + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#regenerateDependencies() + */ + @Override + public void regenerateDependencies(boolean force) throws CoreException { + // A hack for the pre-3.x GCC compilers is to put dummy targets for deps + final IWorkspaceRoot root = CCorePlugin.getWorkspace().getRoot(); + final CoreException[] es = new CoreException[1]; + + toolInfos.accept(new IPathSettingsContainerVisitor() { + @Override + public boolean visit(PathSettingsContainer container) { + ToolInfoHolder h = (ToolInfoHolder) container.getValue(); + // Collect the methods that will need to be called + Vector depExts = new Vector<>(); // Vector of dependency file extensions + IManagedDependencyGenerator2[] postProcessors = new IManagedDependencyGenerator2[h.buildTools.length]; + boolean callPopulateDummyTargets = collectDependencyGeneratorInformation(h, depExts, postProcessors); + + // Is there anyone to call if we do find dependency files? + if (!callPopulateDummyTargets) { + int i; + for (i = 0; i < postProcessors.length; i++) { + if (postProcessors[i] != null) + break; + } + if (i == postProcessors.length) + return true; + } + + IResourceInfo rcInfo = config.getResourceInfo(container.getPath(), false); + for (IPath path : getDependencyMakefiles(h)) { + // The path to search for the dependency makefile + IPath relDepFilePath = topBuildDir.append(path); + IFile depFile = root.getFile(relDepFilePath); + if (depFile == null || !depFile.isAccessible()) + continue; + try { + callDependencyPostProcessors(rcInfo, h, depFile, postProcessors, callPopulateDummyTargets, + true); + } catch (CoreException e) { + es[0] = e; + return false; + } + } + return true; + } + }); + + if (es[0] != null) + throw es[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator#regenerateMakefiles() + */ + @Override + public MultiStatus regenerateMakefiles() throws CoreException { + MultiStatus status; + // Visit the resources in the project + ResourceProxyVisitor visitor = new ResourceProxyVisitor(this, config); + project.accept(visitor, IResource.NONE); + + // See if the user has cancelled the build + checkCancel(); + + // Populate the makefile if any buildable source files have been found in the project + if (getSubdirList().isEmpty()) { + String info = ManagedMakeMessages.getFormattedString("MakefileGenerator.warning.no.source", //$NON-NLS-1$ + project.getName()); + updateMonitor(info); + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.INFO, "", //$NON-NLS-1$ + null); + status.add(new Status(IStatus.INFO, ManagedBuilderCorePlugin.getUniqueIdentifier(), NO_SOURCE_FOLDERS, info, + null)); + return status; + } + + // Create the top-level directory for the build output + ensureTopBuildDir(); + checkCancel(); + + // Get the list of subdirectories + IPath srcsFilePath = topBuildDir.append(SRCSFILE_NAME); + IFile srcsFileHandle = createFile(srcsFilePath); + buildSrcVars.clear(); + buildOutVars.clear(); + buildDepVars.clear(); + topBuildOutVars.clear(); + populateSourcesMakefile(srcsFileHandle); + checkCancel(); + + // Now populate the module makefiles + for (IResource res : getSubdirList()) { + IContainer subDir = (IContainer) res; + try { + populateFragmentMakefile(subDir); + } catch (CoreException e) { + // Probably should ask user if they want to continue + checkCancel(); + continue; + } + checkCancel(); + } + + // Calculate the inputs and outputs of the Tools to be generated in the main makefile + calculateToolInputsOutputs(); + checkCancel(); + + // Create the top-level makefile + IPath makefilePath = topBuildDir.append(MAKEFILE_NAME); + IFile makefileHandle = createFile(makefilePath); + populateTopMakefile(makefileHandle, true); + checkCancel(); + + // Now finish up by adding all the object files + IPath objFilePath = topBuildDir.append(OBJECTS_MAKFILE); + IFile objsFileHandle = createFile(objFilePath); + populateObjectsMakefile(objsFileHandle); + checkCancel(); + + // How did we do + if (!getInvalidDirList().isEmpty()) { + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.WARNING, "", //$NON-NLS-1$ + null); + // Add a new status for each of the bad folders + // TODO: fix error message + for (IResource dir : getInvalidDirList()) { + status.add(new Status(IStatus.WARNING, ManagedBuilderCorePlugin.getUniqueIdentifier(), SPACES_IN_PATH, + dir.getFullPath().toString(), null)); + } + } else { + status = new MultiStatus(ManagedBuilderCorePlugin.getUniqueIdentifier(), IStatus.OK, "", //$NON-NLS-1$ + null); + } + return status; + } + + /************************************************************************* + * M A K E F I L E S P O P U L A T I O N M E T H O D S + ************************************************************************/ + + /** + * This method generates a "fragment" make file (subdir.mk). + * One of these is generated for each project directory/subdirectory + * that contains source files. + */ + protected void populateFragmentMakefile(IContainer module) throws CoreException { + // Calculate the new directory relative to the build output + IPath moduleRelativePath = module.getProjectRelativePath(); + IPath buildRoot = getBuildWorkingDir(); + if (buildRoot == null) { + return; + } + + IPath moduleOutputPath = buildRoot.append(moduleRelativePath); + updateMonitor(ManagedMakeMessages.getFormattedString("MakefileGenerator.message.gen.source.makefile", //$NON-NLS-1$ + moduleOutputPath.toString())); + + // Now create the directory + IPath moduleOutputDir = createDirectory(moduleOutputPath.toString()); + + // Create a module makefile + IFile modMakefile = createFile(moduleOutputDir.append(MODFILE_NAME)); + StringBuffer makeBuf = new StringBuffer(); + makeBuf.append(addFragmentMakefileHeader()); + makeBuf.append(addSources(module)); + + // Save the files + save(makeBuf, modMakefile); + } + + /** + * The makefile generator generates a Macro for each type of output, other than final artifact, + * created by the build. + * + * @param fileHandle The file that should be populated with the output + */ + protected void populateObjectsMakefile(IFile fileHandle) throws CoreException { + + // Master list of "object" dependencies, i.e. dependencies between input files and output files. + StringBuffer macroBuffer = new StringBuffer(); + List valueList; + macroBuffer.append(addDefaultHeader()); + + // Map of macro names (String) to its definition (List of Strings) + HashMap> outputMacros = new HashMap<>(); + + // Add the predefined LIBS, USER_OBJS macros + + // Add the libraries this project depends on + valueList = new ArrayList<>(); + String[] libs = config.getLibs(buildTargetExt); + for (String lib : libs) { + valueList.add(lib); + } + outputMacros.put("LIBS", valueList); //$NON-NLS-1$ + + // Add the extra user-specified objects + valueList = new ArrayList<>(); + String[] userObjs = config.getUserObjects(buildTargetExt); + for (String obj : userObjs) { + valueList.add(obj); + } + outputMacros.put("USER_OBJS", valueList); //$NON-NLS-1$ + + // Write every macro to the file + for (Entry> entry : outputMacros.entrySet()) { + macroBuffer.append(entry.getKey()).append(" :="); //$NON-NLS-1$ + valueList = entry.getValue(); + for (String path : valueList) { + // These macros will also be used within commands. + // Make all the slashes go forward so they aren't + // interpreted as escapes and get lost. + // See https://bugs.eclipse.org/163672. + path = path.replace('\\', '/'); + + path = ensurePathIsGNUMakeTargetRuleCompatibleSyntax(path); + + macroBuffer.append(WHITESPACE); + macroBuffer.append(path); + } + // terminate the macro definition line + macroBuffer.append(NEWLINE); + // leave a blank line before the next macro + macroBuffer.append(NEWLINE); + } + + // For now, just save the buffer that was populated when the rules were created + save(macroBuffer, fileHandle); + + } + + protected void populateSourcesMakefile(IFile fileHandle) throws CoreException { + // Add the comment + StringBuffer buffer = addDefaultHeader(); + + // Determine the set of macros + toolInfos.accept(new IPathSettingsContainerVisitor() { + + @Override + public boolean visit(PathSettingsContainer container) { + ToolInfoHolder h = (ToolInfoHolder) container.getValue(); + ITool[] buildTools = h.buildTools; + HashSet handledInputExtensions = new HashSet<>(); + String buildMacro; + for (ITool buildTool : buildTools) { + if (buildTool.getCustomBuildStep()) + continue; + // Add the known sources macros + String[] extensionsList = buildTool.getAllInputExtensions(); + for (String ext : extensionsList) { + // create a macro of the form "EXTENSION_SRCS :=" + String extensionName = ext; + if (//!getOutputExtensions().contains(extensionName) && + !handledInputExtensions.contains(extensionName)) { + handledInputExtensions.add(extensionName); + buildMacro = getSourceMacroName(extensionName).toString(); + if (!buildSrcVars.containsKey(buildMacro)) { + buildSrcVars.put(buildMacro, new ArrayList()); + } + // Add any generated dependency file macros + IManagedDependencyGeneratorType depType = buildTool + .getDependencyGeneratorForExtension(extensionName); + if (depType != null) { + int calcType = depType.getCalculatorType(); + if (calcType == IManagedDependencyGeneratorType.TYPE_COMMAND + || calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS + || calcType == IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS) { + buildMacro = getDepMacroName(extensionName).toString(); + if (!buildDepVars.containsKey(buildMacro)) { + buildDepVars.put(buildMacro, new GnuDependencyGroupInfo(buildMacro, + (calcType != IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS))); + } + if (!buildOutVars.containsKey(buildMacro)) { + buildOutVars.put(buildMacro, new ArrayList()); + } + } + } + } + } + // Add the specified output build variables + IOutputType[] outTypes = buildTool.getOutputTypes(); + if (outTypes != null && outTypes.length > 0) { + for (IOutputType outputType : outTypes) { + buildMacro = outputType.getBuildVariable(); + if (!buildOutVars.containsKey(buildMacro)) { + buildOutVars.put(buildMacro, new ArrayList()); + } + } + } else { + // For support of pre-CDT 3.0 integrations. + buildMacro = OBJS_MACRO; + if (!buildOutVars.containsKey(buildMacro)) { + buildOutVars.put(buildMacro, new ArrayList()); + } + } + } + return true; + } + }); + // Add the macros to the makefile + for (Entry> entry : buildSrcVars.entrySet()) { + String macroName = entry.getKey(); + buffer.append(macroName).append(WHITESPACE).append(":=").append(WHITESPACE).append(NEWLINE); //$NON-NLS-1$ + } + Set>> set = buildOutVars.entrySet(); + for (Entry> entry : set) { + String macroName = entry.getKey(); + buffer.append(macroName).append(WHITESPACE).append(":=").append(WHITESPACE).append(NEWLINE); //$NON-NLS-1$ + } + + // Add a list of subdirectories to the makefile + buffer.append(NEWLINE).append(addSubdirectories()); + + // Save the file + save(buffer, fileHandle); + } + + /** + * Create the entire contents of the makefile. + * + * @param fileHandle The file to place the contents in. + * @param rebuild FLag signaling that the user is doing a full rebuild + */ + protected void populateTopMakefile(IFile fileHandle, boolean rebuild) throws CoreException { + StringBuffer buffer = new StringBuffer(); + + // Add the header + buffer.append(addTopHeader()); + + // Add the macro definitions + buffer.append(addMacros()); + + // List to collect needed build output variables + List outputVarsAdditionsList = new ArrayList<>(); + + // Determine target rules + StringBuffer targetRules = addTargets(outputVarsAdditionsList, rebuild); + + // Add outputMacros that were added to by the target rules + buffer.append(writeTopAdditionMacros(outputVarsAdditionsList, getTopBuildOutputVars())); + + // Add target rules + buffer.append(targetRules); + + // Save the file + save(buffer, fileHandle); + } + + /************************************************************************* + * M A I N (makefile) M A K E F I L E M E T H O D S + ************************************************************************/ + + /** + * Answers a StringBuffer containing the comment(s) + * for the top-level makefile. + */ + protected StringBuffer addTopHeader() { + return addDefaultHeader(); + } + + /** + */ + private StringBuffer addMacros() { + StringBuffer buffer = new StringBuffer(); + + // Add the ROOT macro + //buffer.append("ROOT := ..").append(NEWLINE); //$NON-NLS-1$ + //buffer.append(NEWLINE); + + // include makefile.init supplementary makefile + buffer.append("-include " + reachProjectRoot() + SEPARATOR + MAKEFILE_INIT).append(NEWLINE); //$NON-NLS-1$ + buffer.append(NEWLINE); + + // Get the clean command from the build model + buffer.append("RM := "); //$NON-NLS-1$ + + // support macros in the clean command + String cleanCommand = config.getCleanCommand(); + + try { + cleanCommand = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + config.getCleanCommand(), EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, + config); + } catch (BuildMacroException e) { + } + + buffer.append(cleanCommand).append(NEWLINE); + + buffer.append(NEWLINE); + + // Now add the source providers + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(SRC_LISTS)) + .append(NEWLINE); + buffer.append("-include sources.mk").append(NEWLINE); //$NON-NLS-1$ + + // Add includes for each subdir in child-subdir-first order (required for makefile rule matching to work). + List subDirList = new ArrayList<>(); + for (IContainer subDir : getSubdirList()) { + String projectRelativePath = subDir.getProjectRelativePath().toString(); + if (!projectRelativePath.isEmpty()) + subDirList.add(0, projectRelativePath); + } + Collections.sort(subDirList, Collections.reverseOrder()); + for (String dir : subDirList) { + buffer.append("-include ").append(escapeWhitespaces(dir)).append(SEPARATOR).append("subdir.mk") //$NON-NLS-1$//$NON-NLS-2$ + .append(NEWLINE); + } + buffer.append("-include subdir.mk").append(NEWLINE); //$NON-NLS-1$ + + buffer.append("-include objects.mk").append(NEWLINE).append(NEWLINE); //$NON-NLS-1$ + + // Include generated dependency makefiles if non-empty AND a "clean" has not been requested + if (!buildDepVars.isEmpty()) { + buffer.append("ifneq ($(MAKECMDGOALS),clean)").append(NEWLINE); //$NON-NLS-1$ + + for (Entry entry : buildDepVars.entrySet()) { + String depsMacro = entry.getKey(); + GnuDependencyGroupInfo info = entry.getValue(); + buffer.append("ifneq ($(strip $(").append(depsMacro).append(")),)").append(NEWLINE); //$NON-NLS-1$ //$NON-NLS-2$ + if (info.conditionallyInclude) { + buffer.append("-include $(").append(depsMacro).append(')').append(NEWLINE); //$NON-NLS-1$ + } else { + buffer.append("include $(").append(depsMacro).append(')').append(NEWLINE); //$NON-NLS-1$ + } + buffer.append("endif").append(NEWLINE); //$NON-NLS-1$ + } + + buffer.append("endif").append(NEWLINE).append(NEWLINE); //$NON-NLS-1$ + } + + // Include makefile.defs supplemental makefile + buffer.append("-include ").append(reachProjectRoot()).append(SEPARATOR).append(MAKEFILE_DEFS).append(NEWLINE); //$NON-NLS-1$ + + final String wildcardFileFmt = "$(wildcard %s)" + WHITESPACE + LINEBREAK; //$NON-NLS-1$ + buffer.append(NEWLINE).append("OPTIONAL_TOOL_DEPS :=").append(WHITESPACE).append(LINEBREAK); //$NON-NLS-1$ + buffer.append(String.format(wildcardFileFmt, reachProjectRoot() + SEPARATOR + MAKEFILE_DEFS)); + buffer.append(String.format(wildcardFileFmt, reachProjectRoot() + SEPARATOR + MAKEFILE_INIT)); + buffer.append(String.format(wildcardFileFmt, reachProjectRoot() + SEPARATOR + MAKEFILE_TARGETS)); + buffer.append(NEWLINE); + + String ext = config.getArtifactExtension(); + // try to resolve the build macros in the artifact extension + try { + ext = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(ext, EMPTY_STRING, + WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } catch (BuildMacroException e) { + } + + String name = config.getArtifactName(); + // try to resolve the build macros in the artifact name + try { + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(name, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + if ((resolved = resolved.trim()).length() > 0) { + name = resolved; + } + } catch (BuildMacroException e) { + } + + String prefix = EMPTY_STRING; + ITool targetTool = config.calculateTargetTool(); + if (targetTool != null) { + prefix = targetTool.getOutputPrefix(); + if (prefix == null) { + prefix = EMPTY_STRING; + } + } + // try to resolve the build macros in the artifact prefix + try { + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(prefix, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + if ((resolved = resolved.trim()).length() > 0) { + prefix = resolved; + } + } catch (BuildMacroException e) { + } + + @SuppressWarnings("nls") + String[][] buildArtifactVars = new String[][] { // + { "BUILD_ARTIFACT_NAME", name }, // + { "BUILD_ARTIFACT_EXTENSION", ext }, // + { "BUILD_ARTIFACT_PREFIX", prefix }, // + { "BUILD_ARTIFACT", + "$(BUILD_ARTIFACT_PREFIX)$(BUILD_ARTIFACT_NAME)$(if $(BUILD_ARTIFACT_EXTENSION),.$(BUILD_ARTIFACT_EXTENSION),)" }, // + }; + + buffer.append(NEWLINE); + for (String[] var : buildArtifactVars) { + buffer.append(var[0]).append(" :="); //$NON-NLS-1$ + if (!var[1].isEmpty()) { + buffer.append(WHITESPACE).append(var[1]); + } + buffer.append(NEWLINE); + } + + return (buffer.append(NEWLINE)); + } + + /** + * Answers a StringBuffer containing all of the required targets to + * properly build the project. + * + * @param outputVarsAdditionsList list to add needed build output variables to + */ + private StringBuffer addTargets(List outputVarsAdditionsList, boolean rebuild) { + StringBuffer buffer = new StringBuffer(); + + // IConfiguration config = info.getDefaultConfiguration(); + + // Assemble the information needed to generate the targets + String prebuildStep = config.getPrebuildStep(); + try { + //try to resolve the build macros in the prebuild step + prebuildStep = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(prebuildStep, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } catch (BuildMacroException e) { + } + prebuildStep = prebuildStep.trim(); // Remove leading and trailing whitespace (and control characters) + + String postbuildStep = config.getPostbuildStep(); + try { + //try to resolve the build macros in the postbuild step + postbuildStep = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(postbuildStep, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + + } catch (BuildMacroException e) { + } + postbuildStep = postbuildStep.trim(); // Remove leading and trailing whitespace (and control characters) + String preannouncebuildStep = config.getPreannouncebuildStep(); + String postannouncebuildStep = config.getPostannouncebuildStep(); + String targets = rebuild ? "clean all" : "all"; //$NON-NLS-1$ //$NON-NLS-2$ + + ITool targetTool = config.calculateTargetTool(); + // if (targetTool == null) { + // targetTool = info.getToolFromOutputExtension(buildTargetExt); + // } + + // Get all the projects the build target depends on + // If this configuration produces a static archive, building the archive doesn't depend on the output + // from any of the referenced configurations + IConfiguration[] refConfigs = new IConfiguration[0]; + if (config.getBuildArtefactType() == null || !ManagedBuildManager.BUILD_ARTEFACT_TYPE_PROPERTY_STATICLIB + .equals(config.getBuildArtefactType().getId())) + refConfigs = ManagedBuildManager.getReferencedConfigurations(config); + + // Add the comment for the "All" target + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(ALL_TARGET)) + .append(NEWLINE); + + if (!prebuildStep.isEmpty() || !postbuildStep.isEmpty()) { + // all: + buffer.append("all").append(COLON).append(NEWLINE); //$NON-NLS-1$ + + String makeNoPrintDir = MAKE + WHITESPACE + NO_PRINT_DIR + WHITESPACE; + buffer.append(TAB).append("+@"); //$NON-NLS-1$ + if (!prebuildStep.isEmpty()) { + buffer.append(makeNoPrintDir).append(PREBUILD).append(WHITESPACE).append(LOGICAL_AND) + .append(WHITESPACE); + } + buffer.append(makeNoPrintDir).append(MAINBUILD); + if (!postbuildStep.isEmpty()) { + buffer.append(WHITESPACE).append(LOGICAL_AND).append(WHITESPACE).append(makeNoPrintDir) + .append(POSTBUILD); + } + + buffer.append(NEWLINE); + + } else { + // all: main-build + buffer.append("all").append(COLON).append(WHITESPACE).append(MAINBUILD).append(NEWLINE); //$NON-NLS-1$ + } + buffer.append(NEWLINE); + + // Add the comment for the "main-build" target + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(MAINBUILD_TARGET)) + .append(NEWLINE); + + // Write out the main-build target first in case someone just runs make + // main-build: + + String outputPrefix = EMPTY_STRING; + if (targetTool != null) { + outputPrefix = targetTool.getOutputPrefix(); + } + buffer.append(MAINBUILD).append(COLON).append(WHITESPACE).append(outputPrefix) + .append(ensurePathIsGNUMakeTargetRuleCompatibleSyntax(buildTargetName)); + if (buildTargetExt.length() > 0) { + buffer.append(DOT).append(buildTargetExt); + } + + // Add the Secondary Outputs to the all target, if any + IOutputType[] secondaryOutputs = config.getToolChain().getSecondaryOutputs(); + if (secondaryOutputs.length > 0) { + buffer.append(WHITESPACE).append(SECONDARY_OUTPUTS); + } + + buffer.append(NEWLINE).append(NEWLINE); + + /* + * The build target may depend on other projects in the workspace. These + * are captured in the deps target: deps: ; + * $(MAKE) [clean all | all]> + */ + // Vector managedProjectOutputs = new Vector(refdProjects.length); + // if (refdProjects.length > 0) { + Vector managedProjectOutputs = new Vector<>(refConfigs.length); + if (refConfigs.length > 0) { + boolean addDeps = true; + // if (refdProjects != null) { + for (IConfiguration depCfg : refConfigs) { + // IProject dep = refdProjects[i]; + if (!depCfg.isManagedBuildOn()) + continue; + + // if (!dep.exists()) continue; + if (addDeps) { + buffer.append("dependents:").append(NEWLINE); //$NON-NLS-1$ + addDeps = false; + } + String buildDir = depCfg.getOwner().getLocation().toString(); + String depTargets = targets; + // if (ManagedBuildManager.manages(dep)) { + // Add the current configuration to the makefile path + // IManagedBuildInfo depInfo = ManagedBuildManager.getBuildInfo(dep); + buildDir += SEPARATOR + depCfg.getName(); + + // Extract the build artifact to add to the dependency list + String depTarget = depCfg.getArtifactName(); + String depExt = depCfg.getArtifactExtension(); + + try { + //try to resolve the build macros in the artifact extension + depExt = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(depExt, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, depCfg); + } catch (BuildMacroException e) { + } + + try { + //try to resolve the build macros in the artifact name + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + depTarget, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, depCfg); + if ((resolved = resolved.trim()).length() > 0) + depTarget = resolved; + } catch (BuildMacroException e) { + } + + String depPrefix = depCfg.getOutputPrefix(depExt); + if (depCfg.needsRebuild()) { + depTargets = "clean all"; //$NON-NLS-1$ + } + String dependency = buildDir + SEPARATOR + depPrefix + depTarget; + if (depExt.length() > 0) { + dependency += DOT + depExt; + } + dependency = escapeWhitespaces(dependency); + managedProjectOutputs.add(dependency); + //} + buffer.append(TAB).append("-cd").append(WHITESPACE).append(escapeWhitespaces(buildDir)) //$NON-NLS-1$ + .append(WHITESPACE).append(LOGICAL_AND).append(WHITESPACE).append("$(MAKE) ").append(depTargets) //$NON-NLS-1$ + .append(NEWLINE); + } + // } + buffer.append(NEWLINE); + } + + // Add the targets tool rules + buffer.append(addTargetsRules(targetTool, outputVarsAdditionsList, managedProjectOutputs)); + + // Add the prebuild step target, if specified + if (prebuildStep.length() > 0) { + buffer.append(PREBUILD).append(COLON).append(NEWLINE); + if (preannouncebuildStep.length() > 0) { + buffer.append(TAB).append(DASH).append(AT).append(escapedEcho(preannouncebuildStep)); + } + buffer.append(TAB).append(DASH).append(prebuildStep).append(NEWLINE); + buffer.append(TAB).append(DASH).append(AT).append(ECHO_BLANK_LINE).append(NEWLINE); + } + + // Add the postbuild step, if specified + if (postbuildStep.length() > 0) { + buffer.append(POSTBUILD).append(COLON).append(NEWLINE); + if (postannouncebuildStep.length() > 0) { + buffer.append(TAB).append(DASH).append(AT).append(escapedEcho(postannouncebuildStep)); + } + buffer.append(TAB).append(DASH).append(postbuildStep).append(NEWLINE); + buffer.append(TAB).append(DASH).append(AT).append(ECHO_BLANK_LINE).append(NEWLINE); + } + + // Add the Secondary Outputs target, if needed + if (secondaryOutputs.length > 0) { + buffer.append(SECONDARY_OUTPUTS).append(COLON); + Vector outs2 = calculateSecondaryOutputs(secondaryOutputs); + for (int i = 0; i < outs2.size(); i++) { + buffer.append(WHITESPACE).append("$(").append(outs2.get(i)).append(')'); //$NON-NLS-1$ + } + buffer.append(NEWLINE).append(NEWLINE); + } + + // Add all the needed dummy and phony targets + buffer.append(".PHONY: all clean dependents").append(WHITESPACE).append(MAINBUILD); //$NON-NLS-1$ + if (prebuildStep.length() > 0) { + buffer.append(WHITESPACE).append(PREBUILD); + } + if (postbuildStep.length() > 0) { + buffer.append(WHITESPACE).append(POSTBUILD); + } + buffer.append(NEWLINE); + for (String output : managedProjectOutputs) { + buffer.append(output).append(COLON).append(NEWLINE); + } + buffer.append(NEWLINE); + + // Include makefile.targets supplemental makefile + buffer.append("-include ").append(reachProjectRoot()).append(SEPARATOR).append(MAKEFILE_TARGETS) //$NON-NLS-1$ + .append(NEWLINE); + + return buffer; + } + + /** + * Returns the targets rules. The targets make file (top makefile) contains: + * 1 the rule for the final target tool + * 2 the rules for all of the tools that use multipleOfType in their primary input type + * 3 the rules for all tools that use the output of #2 tools + * + * @param outputVarsAdditionsList list to add needed build output variables to + * @param managedProjectOutputs Other projects in the workspace that this project depends upon + * @return StringBuffer + */ + private StringBuffer addTargetsRules(ITool targetTool, List outputVarsAdditionsList, + Vector managedProjectOutputs) { + StringBuffer buffer = new StringBuffer(); + // Add the comment + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(BUILD_TOP)) + .append(NEWLINE); + + ToolInfoHolder h = (ToolInfoHolder) toolInfos.getValue(); + ITool[] buildTools = h.buildTools; + boolean[] buildToolsUsed = h.buildToolsUsed; + // Get the target tool and generate the rule + if (targetTool != null) { + // Note that the name of the target we pass to addRuleForTool does not + // appear to be used there (and tool outputs are consulted directly), but + // we quote it anyway just in case it starts to use it in future. + if (addRuleForTool(targetTool, buffer, true, ensurePathIsGNUMakeTargetRuleCompatibleSyntax(buildTargetName), + buildTargetExt, outputVarsAdditionsList, managedProjectOutputs, false)) { + // Mark the target tool as processed + for (int i = 0; i < buildTools.length; i++) { + if (targetTool == buildTools[i]) { + buildToolsUsed[i] = true; + } + } + } + } else { + buffer.append(TAB).append(AT).append(escapedEcho(MESSAGE_NO_TARGET_TOOL + WHITESPACE + OUT_MACRO)); + } + + // Generate the rules for all Tools that specify InputType.multipleOfType, and any Tools that + // consume the output of those tools. This does not apply to pre-3.0 integrations, since + // the only "multipleOfType" tool is the "target" tool + for (int i = 0; i < buildTools.length; i++) { + ITool tool = buildTools[i]; + IInputType type = tool.getPrimaryInputType(); + if (type != null && type.getMultipleOfType()) { + if (!buildToolsUsed[i]) { + addRuleForTool(tool, buffer, false, null, null, outputVarsAdditionsList, null, false); + // Mark the target tool as processed + buildToolsUsed[i] = true; + // Look for tools that consume the output + generateRulesForConsumers(tool, outputVarsAdditionsList, buffer); + } + } + } + + // Add the comment + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(BUILD_TARGETS)) + .append(NEWLINE); + + // Always add a clean target + buffer.append("clean:").append(NEWLINE); //$NON-NLS-1$ + buffer.append(TAB).append("-$(RM)").append(WHITESPACE); //$NON-NLS-1$ + for (Entry> entry : buildOutVars.entrySet()) { + String macroName = entry.getKey(); + buffer.append("$(").append(macroName).append(')'); //$NON-NLS-1$ + } + String outputPrefix = EMPTY_STRING; + if (targetTool != null) { + outputPrefix = targetTool.getOutputPrefix(); + } + String completeBuildTargetName = outputPrefix + buildTargetName; + if (buildTargetExt.length() > 0) { + completeBuildTargetName = completeBuildTargetName + DOT + buildTargetExt; + } + if (completeBuildTargetName.contains(" ")) { //$NON-NLS-1$ + buffer.append(WHITESPACE).append('"').append(completeBuildTargetName).append('"'); + } else { + buffer.append(WHITESPACE).append(completeBuildTargetName); + } + buffer.append(NEWLINE); + buffer.append(TAB).append(DASH).append(AT).append(ECHO_BLANK_LINE).append(NEWLINE); + + return buffer; + } + + /** + * @deprecated Use {@link #addRuleForTool(ITool, StringBuffer, boolean, String, String, List, Vector)} + */ + @Deprecated + protected boolean addRuleForTool(ITool tool, StringBuffer buffer, boolean bTargetTool, String targetName, + String targetExt, List outputVarsAdditionsList, Vector managedProjectOutputs, + boolean bEmitPostBuildStepCall) { + return addRuleForTool(tool, buffer, bTargetTool, targetName, targetExt, outputVarsAdditionsList, + managedProjectOutputs); + } + + /** + * Create the rule + * + * @param buffer Buffer to add makefile rules to + * @param bTargetTool True if this is the target tool + * @param targetName If this is the "targetTool", the target file name, else null + * @param targetExt If this is the "targetTool", the target file extension, else null + * @param outputVarsAdditionsList list to add needed build output variables to + * @param managedProjectOutputs Other projects in the workspace that this project depends upon + * @since 9.3 + */ + protected boolean addRuleForTool(ITool tool, StringBuffer buffer, boolean bTargetTool, String targetName, + String targetExt, List outputVarsAdditionsList, Vector managedProjectOutputs) { + + // Get the tool's inputs and outputs + Vector inputs = new Vector<>(); + Vector dependencies = new Vector<>(); + Vector outputs = new Vector<>(); + Vector enumeratedPrimaryOutputs = new Vector<>(); + Vector enumeratedSecondaryOutputs = new Vector<>(); + Vector outputVariables = new Vector<>(); + Vector additionalTargets = new Vector<>(); + String outputPrefix = EMPTY_STRING; + + if (!getToolInputsOutputs(tool, inputs, dependencies, outputs, enumeratedPrimaryOutputs, + enumeratedSecondaryOutputs, outputVariables, additionalTargets, bTargetTool, managedProjectOutputs)) { + return false; + } + + // If we have no primary output, make all of the secondary outputs the primary output + if (enumeratedPrimaryOutputs.size() == 0) { + enumeratedPrimaryOutputs = enumeratedSecondaryOutputs; + enumeratedSecondaryOutputs.clear(); + } + + // Add the output variables for this tool to our list + outputVarsAdditionsList.addAll(outputVariables); + + // Create the build rule + String buildRule = EMPTY_STRING; + String outflag = tool.getOutputFlag(); + + String primaryOutputs = EMPTY_STRING; + String primaryOutputsQuoted = EMPTY_STRING; + boolean first = true; + for (int i = 0; i < enumeratedPrimaryOutputs.size(); i++) { + String output = enumeratedPrimaryOutputs.get(i); + if (!first) { + primaryOutputs += WHITESPACE; + primaryOutputsQuoted += WHITESPACE; + } + first = false; + primaryOutputs += output; + primaryOutputsQuoted += ensurePathIsGNUMakeTargetRuleCompatibleSyntax(output); + } + + buildRule += (primaryOutputsQuoted + COLON + WHITESPACE); + + first = true; + String calculatedDependencies = EMPTY_STRING; + for (int i = 0; i < dependencies.size(); i++) { + String input = dependencies.get(i); + if (!first) + calculatedDependencies += WHITESPACE; + first = false; + calculatedDependencies += input; + } + buildRule += calculatedDependencies; + buildRule += WHITESPACE + MAKEFILE_NAME; // makefile itself + buildRule += WHITESPACE + OBJECTS_MAKFILE; // objects.mk + buildRule += WHITESPACE + "$(OPTIONAL_TOOL_DEPS)"; //$NON-NLS-1$ // Optional dep to generated makefile extension files + + // We can't have duplicates in a makefile + if (getRuleList().contains(buildRule)) { + } else { + getRuleList().add(buildRule); + buffer.append(buildRule).append(NEWLINE); + if (bTargetTool) { + buffer.append(TAB).append(AT).append(escapedEcho(MESSAGE_START_BUILD + WHITESPACE + OUT_MACRO)); + } + buffer.append(TAB).append(AT).append(escapedEcho(tool.getAnnouncement())); + + // Get the command line for this tool invocation + String[] flags; + try { + flags = tool.getToolCommandFlags(null, null); + } catch (BuildException ex) { + // TODO report error + flags = EMPTY_STRING_ARRAY; + } + String command = tool.getToolCommand(); + try { + //try to resolve the build macros in the tool command + String resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + command, EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(null, null, null, tool)); + if ((resolvedCommand = resolvedCommand.trim()).length() > 0) + command = resolvedCommand; + + } catch (BuildMacroException e) { + } + String[] cmdInputs = inputs.toArray(new String[inputs.size()]); + IManagedCommandLineGenerator gen = tool.getCommandLineGenerator(); + IManagedCommandLineInfo cmdLInfo = gen.generateCommandLineInfo(tool, command, flags, outflag, outputPrefix, + primaryOutputs, cmdInputs, tool.getCommandLinePattern()); + + // The command to build + String buildCmd = null; + if (cmdLInfo == null) { + String toolFlags; + try { + toolFlags = tool.getToolCommandFlagsString(null, null); + } catch (BuildException ex) { + // TODO report error + toolFlags = EMPTY_STRING; + } + buildCmd = command + WHITESPACE + toolFlags + WHITESPACE + outflag + WHITESPACE + outputPrefix + + primaryOutputs + WHITESPACE + IN_MACRO; + } else + buildCmd = cmdLInfo.getCommandLine(); + + // resolve any remaining macros in the command after it has been + // generated + try { + String resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + buildCmd, EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(null, null, null, tool)); + if ((resolvedCommand = resolvedCommand.trim()).length() > 0) + buildCmd = resolvedCommand; + + } catch (BuildMacroException e) { + } + + //buffer.append(TAB).append(AT).append(escapedEcho(buildCmd)); + //buffer.append(TAB).append(AT).append(buildCmd); + buffer.append(TAB).append(buildCmd); + + // TODO + // NOTE WELL: Dependency file generation is not handled for this type of Tool + + // Echo finished message + buffer.append(NEWLINE); + buffer.append(TAB).append(AT).append( + escapedEcho((bTargetTool ? MESSAGE_FINISH_BUILD : MESSAGE_FINISH_FILE) + WHITESPACE + OUT_MACRO)); + buffer.append(TAB).append(AT).append(ECHO_BLANK_LINE); + + // Just emit a blank line + buffer.append(NEWLINE); + } + + // If we have secondary outputs, output dependency rules without commands + if (enumeratedSecondaryOutputs.size() > 0 || additionalTargets.size() > 0) { + String primaryOutput = enumeratedPrimaryOutputs.get(0); + Vector addlOutputs = new Vector<>(); + addlOutputs.addAll(enumeratedSecondaryOutputs); + addlOutputs.addAll(additionalTargets); + for (int i = 0; i < addlOutputs.size(); i++) { + String output = addlOutputs.get(i); + String depLine = output + COLON + WHITESPACE + primaryOutput + WHITESPACE + calculatedDependencies + + NEWLINE; + if (!getDepLineList().contains(depLine)) { + getDepLineList().add(depLine); + buffer.append(depLine); + } + } + buffer.append(NEWLINE); + } + return true; + } + + /** + * @param outputVarsAdditionsList list to add needed build output variables to + * @param buffer buffer to add rules to + */ + private void generateRulesForConsumers(ITool generatingTool, List outputVarsAdditionsList, + StringBuffer buffer) { + // Generate a build rule for any tool that consumes the output of this tool + ToolInfoHolder h = (ToolInfoHolder) toolInfos.getValue(); + ITool[] buildTools = h.buildTools; + boolean[] buildToolsUsed = h.buildToolsUsed; + IOutputType[] outTypes = generatingTool.getOutputTypes(); + for (IOutputType outType : outTypes) { + String[] outExts = outType.getOutputExtensions(generatingTool); + String outVariable = outType.getBuildVariable(); + if (outExts != null) { + for (String outExt : outExts) { + for (int k = 0; k < buildTools.length; k++) { + ITool tool = buildTools[k]; + if (!buildToolsUsed[k]) { + // Also has to match build variables if specified + IInputType inType = tool.getInputType(outExt); + if (inType != null) { + String inVariable = inType.getBuildVariable(); + if ((outVariable == null && inVariable == null) || (outVariable != null + && inVariable != null && outVariable.equals(inVariable))) { + if (addRuleForTool(buildTools[k], buffer, false, null, null, + outputVarsAdditionsList, null, false)) { + buildToolsUsed[k] = true; + // Look for tools that consume the output + generateRulesForConsumers(buildTools[k], outputVarsAdditionsList, buffer); + } + } + } + } + } + } + } + } + } + + protected boolean getToolInputsOutputs(ITool tool, Vector inputs, Vector dependencies, + Vector outputs, Vector enumeratedPrimaryOutputs, Vector enumeratedSecondaryOutputs, + Vector outputVariables, Vector additionalTargets, boolean bTargetTool, + Vector managedProjectOutputs) { + ToolInfoHolder h = (ToolInfoHolder) toolInfos.getValue(); + ITool[] buildTools = h.buildTools; + ManagedBuildGnuToolInfo[] gnuToolInfos = h.gnuToolInfos; + // Get the information regarding the tool's inputs and outputs from the objects + // created by calculateToolInputsOutputs + IManagedBuildGnuToolInfo toolInfo = null; + for (int i = 0; i < buildTools.length; i++) { + if (tool == buildTools[i]) { + toolInfo = gnuToolInfos[i]; + break; + } + } + if (toolInfo == null) + return false; + + // Populate the output Vectors + inputs.addAll(toolInfo.getCommandInputs()); + outputs.addAll(toolInfo.getCommandOutputs()); + enumeratedPrimaryOutputs.addAll(toolInfo.getEnumeratedPrimaryOutputs()); + enumeratedSecondaryOutputs.addAll(toolInfo.getEnumeratedSecondaryOutputs()); + outputVariables.addAll(toolInfo.getOutputVariables()); + + Vector unprocessedDependencies = toolInfo.getCommandDependencies(); + for (String path : unprocessedDependencies) { + dependencies.add(ensurePathIsGNUMakeTargetRuleCompatibleSyntax(path)); + } + additionalTargets.addAll(toolInfo.getAdditionalTargets()); + + if (bTargetTool && managedProjectOutputs != null) { + for (String output : managedProjectOutputs) { + dependencies.add(output); + } + } + return true; + } + + protected Vector calculateSecondaryOutputs(IOutputType[] secondaryOutputs) { + ToolInfoHolder h = (ToolInfoHolder) toolInfos.getValue(); + ITool[] buildTools = h.buildTools; + Vector buildVars = new Vector<>(); + for (int i = 0; i < buildTools.length; i++) { + // Add the specified output build variables + IOutputType[] outTypes = buildTools[i].getOutputTypes(); + if (outTypes != null && outTypes.length > 0) { + for (int j = 0; j < outTypes.length; j++) { + IOutputType outType = outTypes[j]; + // Is this one of the secondary outputs? + // Look for an outputType with this ID, or one with a superclass with this id + thisType: for (int k = 0; k < secondaryOutputs.length; k++) { + IOutputType matchType = outType; + do { + if (matchType.getId().equals(secondaryOutputs[k].getId())) { + buildVars.add(outType.getBuildVariable()); + break thisType; + } + matchType = matchType.getSuperClass(); + } while (matchType != null); + } + } + } + } + return buildVars; + } + + protected boolean isSecondaryOutputVar(ToolInfoHolder h, IOutputType[] secondaryOutputs, String varName) { + ITool[] buildTools = h.buildTools; + for (ITool buildTool : buildTools) { + // Add the specified output build variables + IOutputType[] outTypes = buildTool.getOutputTypes(); + if (outTypes != null && outTypes.length > 0) { + for (IOutputType outType : outTypes) { + // Is this one of the secondary outputs? + // Look for an outputType with this ID, or one with a superclass with this id + for (IOutputType secondaryOutput : secondaryOutputs) { + IOutputType matchType = outType; + do { + if (matchType.getId().equals(secondaryOutput.getId())) { + if (outType.getBuildVariable().equals(varName)) { + return true; + } + } + matchType = matchType.getSuperClass(); + } while (matchType != null); + } + } + } + } + return false; + } + + /************************************************************************* + * S O U R C E S (sources.mk) M A K E F I L E M E T H O D S + ************************************************************************/ + + private StringBuffer addSubdirectories() { + StringBuffer buffer = new StringBuffer(); + // Add the comment + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(MOD_LIST)) + .append(NEWLINE); + + buffer.append("SUBDIRS := ").append(LINEBREAK); //$NON-NLS-1$ + + // Get all the module names + for (IResource container : getSubdirList()) { + updateMonitor(ManagedMakeMessages.getFormattedString("MakefileGenerator.message.adding.source.folder", //$NON-NLS-1$ + container.getFullPath().toString())); + // Check the special case where the module is the project root + if (container.getFullPath() == project.getFullPath()) { + buffer.append(DOT).append(WHITESPACE).append(LINEBREAK); + } else { + IPath path = container.getProjectRelativePath(); + buffer.append(escapeWhitespaces(path.toString())).append(WHITESPACE).append(LINEBREAK); + } + } + + buffer.append(NEWLINE); + return buffer; + } + + /************************************************************************* + * F R A G M E N T (subdir.mk) M A K E F I L E M E T H O D S + ************************************************************************/ + + /** + * Returns a StringBuffer containing the comment(s) + * for a fragment makefile (subdir.mk). + */ + protected StringBuffer addFragmentMakefileHeader() { + return addDefaultHeader(); + } + + /** + * Returns a StringBuffer containing makefile text for all of the sources + * contributed by a container (project directory/subdirectory) to the fragement makefile + * + * @param module project resource directory/subdirectory + * @return StringBuffer generated text for the fragement makefile + */ + 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; //$NON-NLS-1$ + + // For build macros in the configuration, create a map which will map them + // to a string which holds its list of sources. + LinkedHashMap buildVarToRuleStringMap = new LinkedHashMap<>(); + + // Add statements that add the source files in this folder, + // and generated source files, and generated dependency files + // to the build macros + for (Entry> entry : buildSrcVars.entrySet()) { + String macroName = entry.getKey(); + addMacroAdditionPrefix(buildVarToRuleStringMap, macroName, null, false); + + } + for (Entry> entry : buildOutVars.entrySet()) { + String macroName = entry.getKey(); + addMacroAdditionPrefix(buildVarToRuleStringMap, macroName, "./" + relativePath, false); //$NON-NLS-1$ + } + + // String buffers + StringBuffer buffer = new StringBuffer(); // Return buffer + StringBuffer ruleBuffer = new StringBuffer( + COMMENT_SYMBOL + WHITESPACE + ManagedMakeMessages.getResourceString(MOD_RULES) + NEWLINE); + + // Visit the resources in this folder and add each one to a sources macro, and generate a build rule, if appropriate + IResource[] resources = module.members(); + + IResourceInfo rcInfo; + IFolder folder = project.getFolder(computeTopBuildDir(config.getName())); + + for (IResource resource : resources) { + if (resource.getType() == IResource.FILE) { + // Check whether this resource is excluded from build + IPath rcProjRelPath = resource.getProjectRelativePath(); + if (!isSource(rcProjRelPath)) + continue; + rcInfo = config.getResourceInfo(rcProjRelPath, false); + // if( (rcInfo.isExcluded()) ) + // continue; + addFragmentMakefileEntriesForSource(buildVarToRuleStringMap, ruleBuffer, folder, relativePath, resource, + getPathForResource(resource), rcInfo, null, false); + } + } + + // Write out the macro addition entries to the buffer + buffer.append(writeAdditionMacros(buildVarToRuleStringMap)); + return buffer.append(ruleBuffer).append(NEWLINE); + } + + /* (non-Javadoc + * Adds the entries for a particular source file to the fragment makefile + * + * @param buildVarToRuleStringMap map of build variable names to the list of files assigned to the variable + * @param ruleBuffer buffer to add generated nmakefile text to + * @param folder the top level build output directory + * @param relativePath build output directory relative path of the current output directory + * @param resource the source file for this invocation of the tool - this may be null for a generated output + * @param sourceLocation the full path of the source + * @param resConfig the IResourceConfiguration associated with this file or null + * @param varName the build variable to add this invocation's outputs to + * if null, use the file extension to find the name + * @param generatedSource if true, this file was generated by another tool in the tool-chain + */ + protected void addFragmentMakefileEntriesForSource(LinkedHashMap buildVarToRuleStringMap, + StringBuffer ruleBuffer, IFolder folder, String relativePath, IResource resource, IPath sourceLocation, + IResourceInfo rcInfo, String varName, boolean generatedSource) { + + // Determine which tool, if any, builds files with this extension + String ext = sourceLocation.getFileExtension(); + ITool tool = null; + + //TODO: remove + // IResourceConfiguration resConfig = null; + // if(rcInfo instanceof IFileInfo){ + // resConfig = (IFileInfo)rcInfo; + // } + //end remove + + // Use the tool from the resource configuration if there is one + if (rcInfo instanceof IFileInfo) { + IFileInfo fi = (IFileInfo) rcInfo; + ITool[] tools = fi.getToolsToInvoke(); + if (tools != null && tools.length > 0) { + tool = tools[0]; + // if(!tool.getCustomBuildStep()) + addToBuildVar(buildVarToRuleStringMap, ext, varName, relativePath, sourceLocation, generatedSource); + } + } + + ToolInfoHolder h = getToolInfo(rcInfo.getPath()); + ITool buildTools[] = h.buildTools; + + // if(tool == null){ + // for (int j=0; j generatedOutputs = new Vector<>(); // IPath's - build directory relative + Vector generatedDepFiles = new Vector<>(); // IPath's - build directory relative or absolute + addRuleForSource(relativePath, ruleBuffer, resource, sourceLocation, rcInfo, generatedSource, + generatedDepFiles, generatedOutputs); + + // If the rule generates a dependency file(s), add the file(s) to the variable + if (generatedDepFiles.size() > 0) { + for (int k = 0; k < generatedDepFiles.size(); k++) { + IPath generatedDepFile = generatedDepFiles.get(k); + addMacroAdditionFile(buildVarToRuleStringMap, getDepMacroName(ext).toString(), + (generatedDepFile.isAbsolute() ? "" : "./") + //$NON-NLS-1$ //$NON-NLS-2$ + generatedDepFile.toString()); + } + } + + // If the generated outputs of this tool are input to another tool, + // 1. add the output to the appropriate macro + // 2. If the tool does not have multipleOfType input, generate the rule. + + IOutputType outType = tool.getPrimaryOutputType(); + String buildVariable = null; + if (outType != null) { + if (tool.getCustomBuildStep()) { + // TODO: This is somewhat of a hack since a custom build step + // tool does not currently define a build variable + if (generatedOutputs.size() > 0) { + IPath firstOutput = generatedOutputs.get(0); + String firstExt = firstOutput.getFileExtension(); + ToolInfoHolder tmpH = getFolderToolInfo(rcInfo.getPath()); + ITool[] tmpBuildTools = tmpH.buildTools; + for (ITool tmpBuildTool : tmpBuildTools) { + if (tmpBuildTool.buildsFileType(firstExt)) { + String bV = tmpBuildTool.getPrimaryInputType().getBuildVariable(); + if (bV.length() > 0) { + buildVariable = bV; + break; + } + } + } + } + } else { + buildVariable = outType.getBuildVariable(); + } + } else { + // For support of pre-CDT 3.0 integrations. + buildVariable = OBJS_MACRO; + } + + for (int k = 0; k < generatedOutputs.size(); k++) { + IPath generatedOutput; + IResource generateOutputResource; + if (generatedOutputs.get(k).isAbsolute()) { + // TODO: Should we use relative paths when possible (e.g., see MbsMacroSupplier.calculateRelPath) + generatedOutput = generatedOutputs.get(k); + // If this file has an absolute path, then the generateOutputResource will not be correct + // because the file is not under the project. We use this resource in the calls to the dependency generator + generateOutputResource = project.getFile(generatedOutput); + } else { + generatedOutput = getPathForResource(project).append(getBuildWorkingDir()) + .append(generatedOutputs.get(k)); + generateOutputResource = project.getFile(getBuildWorkingDir().append(generatedOutputs.get(k))); + } + IResourceInfo nextRcInfo; + if (rcInfo instanceof IFileInfo) { + nextRcInfo = config.getResourceInfo(rcInfo.getPath().removeLastSegments(1), false); + } else { + nextRcInfo = rcInfo; + } + addFragmentMakefileEntriesForSource(buildVarToRuleStringMap, ruleBuffer, folder, relativePath, + generateOutputResource, generatedOutput, nextRcInfo, buildVariable, true); + } + } + } else { + // If this is a secondary input, add it to build vars + if (varName == null) { + for (ITool buildTool : buildTools) { + if (buildTool.isInputFileType(ext)) { + addToBuildVar(buildVarToRuleStringMap, ext, varName, relativePath, sourceLocation, + generatedSource); + break; + } + } + } + // If this generated output is identified as a secondary output, add the file to the build variable + else { + IOutputType[] secondaryOutputs = config.getToolChain().getSecondaryOutputs(); + if (secondaryOutputs.length > 0) { + if (isSecondaryOutputVar(h, secondaryOutputs, varName)) { + addMacroAdditionFile(buildVarToRuleStringMap, varName, relativePath, sourceLocation, + generatedSource); + } + } + } + } + } + + /** + * Gets a path for a resource by extracting the Path field from its + * location URI. + * @return IPath + * @since 6.0 + */ + protected IPath getPathForResource(IResource resource) { + return new Path(resource.getLocationURI().getPath()); + } + + /** + * Adds the source file to the appropriate build variable + * + * @param buildVarToRuleStringMap map of build variable names to the list of files assigned to the variable + * @param ext the file extension of the file + * @param varName the build variable to add this invocation's outputs to + * if null, use the file extension to find the name + * @param relativePath build output directory relative path of the current output directory + * @param sourceLocation the full path of the source + * @param generatedSource if true, this file was generated by another tool in the tool-chain + */ + protected void addToBuildVar(LinkedHashMap buildVarToRuleStringMap, String ext, String varName, + String relativePath, IPath sourceLocation, boolean generatedSource) { + List varList = null; + if (varName == null) { + // Get the proper source build variable based upon the extension + varName = getSourceMacroName(ext).toString(); + varList = buildSrcVars.get(varName); + } else { + varList = buildOutVars.get(varName); + } + // Add the resource to the list of all resources associated with a variable. + // Do not allow duplicates - there is no reason to and it can be 'bad' - + // e.g., having the same object in the OBJS list can cause duplicate symbol errors from the linker + if ((varList != null) && !(varList.contains(sourceLocation))) { + // Since we don't know how these files will be used, we store them using a "location" + // path rather than a relative path + varList.add(sourceLocation); + if (!buildVarToRuleStringMap.containsKey(varName)) { + // TODO - is this an error? + } else { + // Add the resource name to the makefile line that adds resources to the build variable + addMacroAdditionFile(buildVarToRuleStringMap, varName, relativePath, sourceLocation, generatedSource); + } + } + } + + private IManagedCommandLineInfo generateToolCommandLineInfo(ITool tool, String sourceExtension, String[] flags, + String outputFlag, String outputPrefix, String outputName, String[] inputResources, IPath inputLocation, + IPath outputLocation) { + + String cmd = tool.getToolCommand(); + //try to resolve the build macros in the tool command + try { + String resolvedCommand = null; + + if ((inputLocation != null && inputLocation.toString().indexOf(" ") != -1) || //$NON-NLS-1$ + (outputLocation != null && outputLocation.toString().indexOf(" ") != -1)) //$NON-NLS-1$ + { + resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValue(cmd, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(inputLocation, outputLocation, null, tool)); + } + + else { + resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(cmd, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(inputLocation, outputLocation, null, tool)); + } + if ((resolvedCommand = resolvedCommand.trim()).length() > 0) + cmd = resolvedCommand; + + } catch (BuildMacroException e) { + } + + IManagedCommandLineGenerator gen = tool.getCommandLineGenerator(); + return gen.generateCommandLineInfo(tool, cmd, flags, outputFlag, outputPrefix, outputName, inputResources, + tool.getCommandLinePattern()); + + } + + /** + * Create a rule for this source file. We create a pattern rule if possible. + * + * This is an example of a pattern rule: + * + * /%.: ..//%. + * @echo Building file: $< + * @echo Invoking tool xxx + * @echo $@ $< + * @ $@ $< && \ + * echo -n $(@:%.o=%.d) ' /' >> $(@:%.o=%.d) && \ + * -P -MM -MG $< >> $(@:%.o=%.d) + * @echo Finished building: $< + * @echo ' ' + * + * Note that the macros 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: ../source1/%.cpp + * @echo Building file: $< + * @echo Invoking tool xxx + * @echo g++ -g -O2 -c -I/cygdrive/c/eclipse/workspace/Project/headers -o$@ $< + * @g++ -g -O2 -c -I/cygdrive/c/eclipse/workspace/Project/headers -o$@ $< && \ + * echo -n $(@:%.o=%.d) ' source1/' >> $(@:%.o=%.d) && \ + * g++ -P -MM -MG -g -O2 -c -I/cygdrive/c/eclipse/workspace/Project/headers $< >> $(@:%.o=%.d) + * @echo Finished building: $< + * @echo ' ' + * + * @param relativePath top build output directory relative path of the current output directory + * @param buffer buffer to populate with the build rule + * @param resource the source file for this invocation of the tool + * @param sourceLocation the full path of the source + * @param rcInfo the IResourceInfo associated with this file or null + * @param generatedSource true if the resource is a generated output + * @param enumeratedOutputs vector of the filenames that are the output of this rule + */ + protected void addRuleForSource(String relativePath, StringBuffer buffer, IResource resource, IPath sourceLocation, + IResourceInfo rcInfo, boolean generatedSource, Vector generatedDepFiles, + Vector enumeratedOutputs) { + + String fileName = sourceLocation.removeFileExtension().lastSegment(); + String inputExtension = sourceLocation.getFileExtension(); + String outputExtension = null; + + ITool tool = null; + if (rcInfo instanceof IFileInfo) { + IFileInfo fi = (IFileInfo) rcInfo; + ITool[] tools = fi.getToolsToInvoke(); + if (tools != null && tools.length > 0) { + tool = tools[0]; + } + } else { + IFolderInfo foInfo = (IFolderInfo) rcInfo; + tool = foInfo.getToolFromInputExtension(inputExtension); + } + + ToolInfoHolder h = getToolInfo(rcInfo.getPath()); + + if (tool != null) + outputExtension = tool.getOutputExtension(inputExtension); + if (outputExtension == null) + outputExtension = EMPTY_STRING; + + // Get the dependency generator information for this tool and file extension + IManagedDependencyGenerator oldDepGen = null; // This interface is deprecated but still supported + IManagedDependencyGenerator2 depGen = null; // This is the recommended interface + IManagedDependencyInfo depInfo = null; + IManagedDependencyCommands depCommands = null; + IManagedDependencyPreBuild depPreBuild = null; + IPath[] depFiles = null; + boolean doDepGen = false; + { + IManagedDependencyGeneratorType t = null; + if (tool != null) + t = tool.getDependencyGeneratorForExtension(inputExtension); + if (t != null) { + int calcType = t.getCalculatorType(); + if (calcType <= IManagedDependencyGeneratorType.TYPE_OLD_TYPE_LIMIT) { + oldDepGen = (IManagedDependencyGenerator) t; + doDepGen = (calcType == IManagedDependencyGeneratorType.TYPE_COMMAND); + if (doDepGen) { + IPath depFile = Path.fromOSString(relativePath + fileName + DOT + DEP_EXT); + getDependencyMakefiles(h).add(depFile); + generatedDepFiles.add(depFile); + } + } else { + depGen = (IManagedDependencyGenerator2) t; + doDepGen = (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS); + IBuildObject buildContext = rcInfo;//(resConfig != null) ? (IBuildObject)resConfig : (IBuildObject)config; + + depInfo = depGen.getDependencySourceInfo(resource.getProjectRelativePath(), resource, buildContext, + tool, getBuildWorkingDir()); + + if (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS) { + depCommands = (IManagedDependencyCommands) depInfo; + depFiles = depCommands.getDependencyFiles(); + } else if (calcType == IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS) { + depPreBuild = (IManagedDependencyPreBuild) depInfo; + depFiles = depPreBuild.getDependencyFiles(); + } + if (depFiles != null) { + for (IPath depFile : depFiles) { + getDependencyMakefiles(h).add(depFile); + generatedDepFiles.add(depFile); + } + } + } + } + } + + // Figure out the output paths + String optDotExt = EMPTY_STRING; + if (outputExtension.length() > 0) + optDotExt = DOT + outputExtension; + + Vector ruleOutputs = new Vector<>(); + Vector enumeratedPrimaryOutputs = new Vector<>(); // IPaths relative to the top build directory + Vector enumeratedSecondaryOutputs = new Vector<>(); // IPaths relative to the top build directory + calculateOutputsForSource(tool, relativePath, resource, sourceLocation, ruleOutputs, enumeratedPrimaryOutputs, + enumeratedSecondaryOutputs); + enumeratedOutputs.addAll(enumeratedPrimaryOutputs); + enumeratedOutputs.addAll(enumeratedSecondaryOutputs); + String primaryOutputName = null; + if (enumeratedPrimaryOutputs.size() > 0) { + primaryOutputName = escapeWhitespaces(enumeratedPrimaryOutputs.get(0).toString()); + } else { + primaryOutputName = escapeWhitespaces(relativePath + fileName + optDotExt); + } + String otherPrimaryOutputs = EMPTY_STRING; + for (int i = 1; i < enumeratedPrimaryOutputs.size(); i++) { // Starting with 1 is intentional + otherPrimaryOutputs += WHITESPACE + escapeWhitespaces(enumeratedPrimaryOutputs.get(i).toString()); + } + + // Output file location needed for the file-specific build macros + IPath outputLocation = Path.fromOSString(primaryOutputName); + if (!outputLocation.isAbsolute()) { + outputLocation = getPathForResource(project).append(getBuildWorkingDir()).append(primaryOutputName); + } + + // A separate rule is needed for the resource in the case where explicit file-specific macros + // are referenced, or if the resource contains special characters in its path (e.g., whitespace) + + /* fix for 137674 + * + * We only need an explicit rule if one of the following is true: + * - The resource is linked, and its full path to its real location contains special characters + * - The resource is not linked, but its project relative path contains special characters + */ + + boolean resourceNameRequiresExplicitRule = (resource.isLinked() + && containsSpecialCharacters(sourceLocation.toString())) + || (!resource.isLinked() && containsSpecialCharacters(resource.getProjectRelativePath().toString())); + + boolean needExplicitRuleForFile = resourceNameRequiresExplicitRule + || BuildMacroProvider.getReferencedExplitFileMacros(tool).length > 0 + || BuildMacroProvider.getReferencedExplitFileMacros(tool.getToolCommand(), + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)).length > 0; + + // Get and resolve the command + String cmd = tool.getToolCommand(); + + try { + String resolvedCommand = null; + if (!needExplicitRuleForFile) { + resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(cmd, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } else { + // if we need an explicit rule then don't use any builder + // variables, resolve everything + // to explicit strings + resolvedCommand = ManagedBuildManager.getBuildMacroProvider().resolveValue(cmd, EMPTY_STRING, + WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + if ((resolvedCommand = resolvedCommand.trim()).length() > 0) + cmd = resolvedCommand; + + } catch (BuildMacroException e) { + } + + String defaultOutputName = EMPTY_STRING; + String primaryDependencyName = EMPTY_STRING; + String patternPrimaryDependencyName = EMPTY_STRING; + String home = (generatedSource) ? DOT : reachProjectRoot(); + String resourcePath = null; + boolean patternRule = true; + boolean isItLinked = false; + if (resource.isLinked(IResource.CHECK_ANCESTORS)) { + // it IS linked, so use the actual location + isItLinked = true; + resourcePath = sourceLocation.toString(); + // Need a hardcoded rule, not a pattern rule, as a linked file + // can reside in any path + defaultOutputName = escapeWhitespaces(relativePath + fileName + optDotExt); + primaryDependencyName = escapeWhitespaces(resourcePath); + patternRule = false; + } else { + // Use the relative path (not really needed to store per se but in the future someone may want this) + resourcePath = relativePath; + // The rule and command to add to the makefile + if (rcInfo instanceof IFileInfo || needExplicitRuleForFile) { + // Need a hardcoded rule, not a pattern rule + defaultOutputName = escapeWhitespaces(resourcePath + fileName + optDotExt); + patternRule = false; + } else { + defaultOutputName = relativePath + WILDCARD + optDotExt; + } + primaryDependencyName = escapeWhitespaces( + home + SEPARATOR + resourcePath + fileName + DOT + inputExtension); + patternPrimaryDependencyName = home + SEPARATOR + resourcePath + WILDCARD + DOT + inputExtension; + } // end fix for PR 70491 + + // If the tool specifies a dependency calculator of TYPE_BUILD_COMMANDS, ask whether + // the dependency commands are "generic" (i.e., we can use a pattern rule) + boolean needExplicitDependencyCommands = false; + if (depCommands != null) { + needExplicitDependencyCommands = !depCommands.areCommandsGeneric(); + } + + // If we still think that we are using a pattern rule, check a few more things + if (patternRule) { + patternRule = false; + // Make sure that at least one of the rule outputs contains a %. + for (int i = 0; i < ruleOutputs.size(); i++) { + String ruleOutput = ruleOutputs.get(i).toString(); + if (ruleOutput.indexOf('%') >= 0) { + patternRule = true; + break; + } + } + if (patternRule) { + patternRule = !needExplicitDependencyCommands; + } + } + + // Begin building the rule for this source file + String buildRule = EMPTY_STRING; + + if (patternRule) { + if (ruleOutputs.size() == 0) { + buildRule += defaultOutputName; + } else { + boolean first = true; + for (int i = 0; i < ruleOutputs.size(); i++) { + String ruleOutput = ruleOutputs.get(i).toString(); + if (ruleOutput.indexOf('%') >= 0) { + if (first) { + first = false; + } else { + buildRule += WHITESPACE; + } + buildRule += ruleOutput; + } + } + } + } else { + buildRule += primaryOutputName; + } + + String buildRuleDependencies = primaryDependencyName; + String patternBuildRuleDependencies = patternPrimaryDependencyName; + + // Other additional inputs + // Get any additional dependencies specified for the tool in other InputType elements and AdditionalInput elements + IPath[] addlDepPaths = tool.getAdditionalDependencies(); + for (IPath addlDepPath : addlDepPaths) { + // Translate the path from project relative to build directory relative + IPath addlPath = addlDepPath; + if (!(addlPath.toString().startsWith("$("))) { //$NON-NLS-1$ + if (!addlPath.isAbsolute()) { + IPath tempPath = project.getLocation().append(new Path(ensureUnquoted(addlPath.toString()))); + if (tempPath != null) { + addlPath = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), tempPath); + } + } + } + String suitablePath = ensurePathIsGNUMakeTargetRuleCompatibleSyntax(addlPath.toString()); + buildRuleDependencies += WHITESPACE + suitablePath; + patternBuildRuleDependencies += WHITESPACE + suitablePath; + } + + buildRule += COLON + WHITESPACE + (patternRule ? patternBuildRuleDependencies : buildRuleDependencies) + + WHITESPACE + escapeWhitespaces(relativePath + MODFILE_NAME); + + // No duplicates in a makefile. If we already have this rule, don't add it or the commands to build the file + if (getRuleList().contains(buildRule)) { + // TODO: Should we assert that this is a pattern rule? + } else { + getRuleList().add(buildRule); + + // Echo starting message + buffer.append(buildRule).append(NEWLINE); + buffer.append(TAB).append(AT).append(escapedEcho(MESSAGE_START_FILE + WHITESPACE + IN_MACRO)); + buffer.append(TAB).append(AT).append(escapedEcho(tool.getAnnouncement())); + + // If the tool specifies a dependency calculator of TYPE_BUILD_COMMANDS, ask whether + // there are any pre-tool commands. + if (depCommands != null) { + String[] preToolCommands = depCommands.getPreToolDependencyCommands(); + if (preToolCommands != null && preToolCommands.length > 0) { + for (String preCmd : preToolCommands) { + try { + String resolvedCommand; + IBuildMacroProvider provider = ManagedBuildManager.getBuildMacroProvider(); + if (!needExplicitRuleForFile) { + resolvedCommand = provider.resolveValueToMakefileFormat(preCmd, EMPTY_STRING, + WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } else { + // if we need an explicit rule then don't use any builder + // variables, resolve everything to explicit strings + resolvedCommand = provider.resolveValue(preCmd, EMPTY_STRING, WHITESPACE, + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + if (resolvedCommand != null) + buffer.append(resolvedCommand).append(NEWLINE); + } catch (BuildMacroException e) { + } + } + } + } + + // Generate the command line + + Vector inputs = new Vector<>(); + inputs.add(IN_MACRO); + + // Other additional inputs + // Get any additional dependencies specified for the tool in other InputType elements and AdditionalInput elements + IPath[] addlInputPaths = getAdditionalResourcesForSource(tool); + for (IPath addlInputPath : addlInputPaths) { + // Translate the path from project relative to build directory relative + IPath addlPath = addlInputPath; + if (!(addlPath.toString().startsWith("$("))) { //$NON-NLS-1$ + if (!addlPath.isAbsolute()) { + IPath tempPath = getPathForResource(project).append(addlPath); + if (tempPath != null) { + addlPath = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), tempPath); + } + } + } + inputs.add(addlPath.toString()); + } + String[] inputStrings = inputs.toArray(new String[inputs.size()]); + + String[] flags = null; + // Get the tool command line options + try { + flags = tool.getToolCommandFlags(sourceLocation, outputLocation); + } catch (BuildException ex) { + // TODO add some routines to catch this + flags = EMPTY_STRING_ARRAY; + } + + // If we have a TYPE_BUILD_COMMANDS dependency generator, determine if there are any options that + // it wants added to the command line + if (depCommands != null) { + flags = addDependencyOptions(depCommands, flags); + } + + IManagedCommandLineInfo cmdLInfo = null; + String outflag = null; + String outputPrefix = null; + + if (rcInfo instanceof IFileInfo || needExplicitRuleForFile || needExplicitDependencyCommands) { + outflag = tool.getOutputFlag(); + outputPrefix = tool.getOutputPrefix(); + + // Call the command line generator + IManagedCommandLineGenerator cmdLGen = tool.getCommandLineGenerator(); + cmdLInfo = cmdLGen.generateCommandLineInfo(tool, cmd, flags, outflag, outputPrefix, + OUT_MACRO + otherPrimaryOutputs, inputStrings, tool.getCommandLinePattern()); + + } else { + outflag = tool.getOutputFlag();//config.getOutputFlag(outputExtension); + outputPrefix = tool.getOutputPrefix();//config.getOutputPrefix(outputExtension); + + // Call the command line generator + cmdLInfo = generateToolCommandLineInfo(tool, inputExtension, flags, outflag, outputPrefix, + OUT_MACRO + otherPrimaryOutputs, inputStrings, sourceLocation, outputLocation); + } + + // The command to build + String buildCmd; + if (cmdLInfo != null) { + buildCmd = cmdLInfo.getCommandLine(); + } else { + StringBuffer buildFlags = new StringBuffer(); + for (String flag : flags) { + if (flag != null) { + buildFlags.append(flag).append(WHITESPACE); + } + } + buildCmd = cmd + WHITESPACE + buildFlags.toString().trim() + WHITESPACE + outflag + WHITESPACE + + outputPrefix + OUT_MACRO + otherPrimaryOutputs + WHITESPACE + IN_MACRO; + } + + // resolve any remaining macros in the command after it has been + // generated + try { + String resolvedCommand; + IBuildMacroProvider provider = ManagedBuildManager.getBuildMacroProvider(); + if (!needExplicitRuleForFile) { + resolvedCommand = provider.resolveValueToMakefileFormat(buildCmd, EMPTY_STRING, WHITESPACE, + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } else { + // if we need an explicit rule then don't use any builder + // variables, resolve everything to explicit strings + resolvedCommand = provider.resolveValue(buildCmd, EMPTY_STRING, WHITESPACE, + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + if ((resolvedCommand = resolvedCommand.trim()).length() > 0) + buildCmd = resolvedCommand; + + } catch (BuildMacroException e) { + } + + //buffer.append(TAB).append(AT).append(escapedEcho(buildCmd)); + //buffer.append(TAB).append(AT).append(buildCmd); + buffer.append(TAB).append(buildCmd); + + // Determine if there are any dependencies to calculate + if (doDepGen) { + // Get the dependency rule out of the generator + String[] depCmds = null; + if (oldDepGen != null) { + depCmds = new String[1]; + depCmds[0] = oldDepGen.getDependencyCommand(resource, ManagedBuildManager.getBuildInfo(project)); + } else { + if (depCommands != null) { + depCmds = depCommands.getPostToolDependencyCommands(); + } + } + + if (depCmds != null) { + for (String depCmd : depCmds) { + // Resolve any macros in the dep command after it has been generated. + // Note: do not trim the result because it will strip out necessary tab characters. + buffer.append(WHITESPACE).append(LOGICAL_AND).append(WHITESPACE).append(LINEBREAK); + try { + if (!needExplicitRuleForFile) { + depCmd = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + depCmd, EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + else { + depCmd = ManagedBuildManager.getBuildMacroProvider().resolveValue(depCmd, EMPTY_STRING, + WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + } catch (BuildMacroException e) { + } + + buffer.append(depCmd); + } + } + } + + // Echo finished message + buffer.append(NEWLINE); + buffer.append(TAB).append(AT).append(escapedEcho(MESSAGE_FINISH_FILE + WHITESPACE + IN_MACRO)); + buffer.append(TAB).append(AT).append(ECHO_BLANK_LINE).append(NEWLINE); + } + + // Determine if there are calculated dependencies + IPath[] addlDeps = null; // IPath's that are relative to the build directory + IPath[] addlTargets = null; // IPath's that are relative to the build directory + String calculatedDependencies = null; + boolean addedDepLines = false; + String depLine; + if (oldDepGen != null && oldDepGen.getCalculatorType() != IManagedDependencyGeneratorType.TYPE_COMMAND) { + addlDeps = oldCalculateDependenciesForSource(oldDepGen, tool, relativePath, resource); + } else { + if (depGen != null && depGen.getCalculatorType() == IManagedDependencyGeneratorType.TYPE_CUSTOM) { + if (depInfo instanceof IManagedDependencyCalculator) { + IManagedDependencyCalculator depCalculator = (IManagedDependencyCalculator) depInfo; + addlDeps = calculateDependenciesForSource(depCalculator); + addlTargets = depCalculator.getAdditionalTargets(); + } + } + } + + if (addlDeps != null && addlDeps.length > 0) { + calculatedDependencies = ""; //$NON-NLS-1$ + for (IPath addlDep : addlDeps) { + calculatedDependencies += WHITESPACE + escapeWhitespaces(addlDep.toString()); + } + } + + if (calculatedDependencies != null) { + depLine = primaryOutputName + COLON + calculatedDependencies + NEWLINE; + if (!getDepLineList().contains(depLine)) { + getDepLineList().add(depLine); + addedDepLines = true; + buffer.append(depLine); + } + } + + // Add any additional outputs here using dependency lines + Vector addlOutputs = new Vector<>(); + if (enumeratedPrimaryOutputs.size() > 1) { + // Starting with 1 is intentional in order to skip the primary output + for (int i = 1; i < enumeratedPrimaryOutputs.size(); i++) + addlOutputs.add(enumeratedPrimaryOutputs.get(i)); + } + addlOutputs.addAll(enumeratedSecondaryOutputs); + if (addlTargets != null) { + for (IPath addlTarget : addlTargets) + addlOutputs.add(addlTarget); + } + for (int i = 0; i < addlOutputs.size(); i++) { + depLine = escapeWhitespaces(addlOutputs.get(i).toString()) + COLON + WHITESPACE + primaryOutputName; + if (calculatedDependencies != null) + depLine += calculatedDependencies; + depLine += NEWLINE; + if (!getDepLineList().contains(depLine)) { + getDepLineList().add(depLine); + addedDepLines = true; + buffer.append(depLine); + } + } + if (addedDepLines) { + buffer.append(NEWLINE); + } + + // If we are using a dependency calculator of type TYPE_PREBUILD_COMMANDS, + // get the rule to build the dependency file + if (depPreBuild != null && depFiles != null) { + addedDepLines = false; + String[] preBuildCommands = depPreBuild.getDependencyCommands(); + if (preBuildCommands != null) { + depLine = ""; //$NON-NLS-1$ + // Can we use a pattern rule? + patternRule = !isItLinked && !needExplicitRuleForFile && depPreBuild.areCommandsGeneric(); + // Begin building the rule + for (int i = 0; i < depFiles.length; i++) { + if (i > 0) + depLine += WHITESPACE; + if (patternRule) { + optDotExt = EMPTY_STRING; + String depExt = depFiles[i].getFileExtension(); + if (depExt != null && depExt.length() > 0) + optDotExt = DOT + depExt; + depLine += escapeWhitespaces(relativePath + WILDCARD + optDotExt); + } else { + depLine += escapeWhitespaces((depFiles[i]).toString()); + } + } + depLine += COLON + WHITESPACE + (patternRule ? patternBuildRuleDependencies : buildRuleDependencies) + + WHITESPACE + escapeWhitespaces(relativePath + MODFILE_NAME); + if (!getDepRuleList().contains(depLine)) { + getDepRuleList().add(depLine); + addedDepLines = true; + buffer.append(depLine).append(NEWLINE); + buffer.append(TAB).append(AT) + .append(escapedEcho(MESSAGE_START_DEPENDENCY + WHITESPACE + OUT_MACRO)); + for (String preBuildCommand : preBuildCommands) { + depLine = preBuildCommand; + // Resolve macros + try { + if (!needExplicitRuleForFile) { + depLine = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + depLine, EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + else { + depLine = ManagedBuildManager.getBuildMacroProvider().resolveValue(depLine, + EMPTY_STRING, WHITESPACE, IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, outputLocation, null, tool)); + } + + } catch (BuildMacroException e) { + } + //buffer.append(TAB).append(AT).append(escapedEcho(depLine)); + //buffer.append(TAB).append(AT).append(depLine).append(NEWLINE); + buffer.append(TAB).append(depLine).append(NEWLINE); + } + } + if (addedDepLines) { + buffer.append(TAB).append(AT).append(ECHO_BLANK_LINE).append(NEWLINE); + } + } + } + } + + /* + * Add any dependency calculator options to the tool options + */ + private String[] addDependencyOptions(IManagedDependencyCommands depCommands, String[] flags) { + String[] depOptions = depCommands.getDependencyCommandOptions(); + if (depOptions != null && depOptions.length > 0) { + int flagsLen = flags.length; + String[] flagsCopy = new String[flags.length + depOptions.length]; + for (int i = 0; i < flags.length; i++) { + flagsCopy[i] = flags[i]; + } + for (int i = 0; i < depOptions.length; i++) { + flagsCopy[i + flagsLen] = depOptions[i]; + } + flags = flagsCopy; + } + return flags; + } + + /** + * Returns any additional resources specified for the tool in other InputType elements and AdditionalInput elements + */ + protected IPath[] getAdditionalResourcesForSource(ITool tool) { + List allRes = new ArrayList<>(); + IInputType[] types = tool.getInputTypes(); + for (IInputType type : types) { + // Additional resources come from 2 places. + // 1. From AdditionalInput childen + IPath[] res = type.getAdditionalResources(); + for (IPath re : res) { + allRes.add(re); + } + // 2. From InputTypes that other than the primary input type + if (!type.getPrimaryInput() && type != tool.getPrimaryInputType()) { + String var = type.getBuildVariable(); + if (var != null && var.length() > 0) { + allRes.add(Path.fromOSString("$(" + type.getBuildVariable() + ")")); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + // Use file extensions + String[] typeExts = type.getSourceExtensions(tool); + for (IResource projectResource : projectResources) { + if (projectResource.getType() == IResource.FILE) { + String fileExt = projectResource.getFileExtension(); + if (fileExt == null) { + fileExt = ""; //$NON-NLS-1$ + } + for (String typeExt : typeExts) { + if (fileExt.equals(typeExt)) { + allRes.add(projectResource.getProjectRelativePath()); + break; + } + } + } + } + } + + // If an assignToOption has been specified, set the value of the option to the inputs + IOption assignToOption = tool.getOptionBySuperClassId(type.getAssignToOptionId()); + IOption option = tool.getOptionBySuperClassId(type.getOptionId()); + if (assignToOption != null && option == null) { + try { + int optType = assignToOption.getValueType(); + IResourceInfo rcInfo = tool.getParentResourceInfo(); + if (rcInfo != null) { + if (optType == IOption.STRING) { + String optVal = ""; //$NON-NLS-1$ + for (int j = 0; j < allRes.size(); j++) { + if (j != 0) { + optVal += " "; //$NON-NLS-1$ + } + String resPath = allRes.get(j).toString(); + if (!resPath.startsWith("$(")) { //$NON-NLS-1$ + IResource addlResource = project.getFile(resPath); + if (addlResource != null) { + IPath addlPath = addlResource.getLocation(); + if (addlPath != null) { + resPath = ManagedBuildManager + .calculateRelativePath(getTopBuildDir(), addlPath).toString(); + } + } + } + optVal += ManagedBuildManager + .calculateRelativePath(getTopBuildDir(), Path.fromOSString(resPath)) + .toString(); + } + ManagedBuildManager.setOption(rcInfo, tool, assignToOption, optVal); + } else if (optType == IOption.STRING_LIST || optType == IOption.LIBRARIES + || optType == IOption.OBJECTS || optType == IOption.INCLUDE_FILES + || optType == IOption.LIBRARY_PATHS || optType == IOption.LIBRARY_FILES + || optType == IOption.MACRO_FILES) { + //TODO: do we need to do anything with undefs here? + // Note that the path(s) must be translated from project relative + // to top build directory relative + String[] paths = new String[allRes.size()]; + for (int j = 0; j < allRes.size(); j++) { + paths[j] = allRes.get(j).toString(); + if (!paths[j].startsWith("$(")) { //$NON-NLS-1$ + IResource addlResource = project.getFile(paths[j]); + if (addlResource != null) { + IPath addlPath = addlResource.getLocation(); + if (addlPath != null) { + paths[j] = ManagedBuildManager + .calculateRelativePath(getTopBuildDir(), addlPath).toString(); + } + } + } + } + ManagedBuildManager.setOption(rcInfo, tool, assignToOption, paths); + } else if (optType == IOption.BOOLEAN) { + boolean b = false; + if (allRes.size() > 0) + b = true; + ManagedBuildManager.setOption(rcInfo, tool, assignToOption, b); + } else if (optType == IOption.ENUMERATED || optType == IOption.TREE) { + if (allRes.size() > 0) { + String s = allRes.get(0).toString(); + ManagedBuildManager.setOption(rcInfo, tool, assignToOption, s); + } + } + allRes.clear(); + } + } catch (BuildException ex) { + } + } + } + } + return allRes.toArray(new IPath[allRes.size()]); + } + + /** + * Returns the output IPaths for this invocation of the tool with the specified source file + * + * The priorities for determining the names of the outputs of a tool are: + * 1. If the tool is the build target and primary output, use artifact name & extension - + * This case does not apply here... + * 2. If an option is specified, use the value of the option + * 3. If a nameProvider is specified, call it + * 4. If outputNames is specified, use it + * 5. Use the name pattern to generate a transformation macro + * so that the source names can be transformed into the target names + * using the built-in string substitution functions of make. + * + * @param relativePath build output directory relative path of the current output directory + * @param ruleOutputs Vector of rule IPaths that are relative to the build directory + * @param enumeratedPrimaryOutputs Vector of IPaths of primary outputs + * that are relative to the build directory + * @param enumeratedSecondaryOutputs Vector of IPaths of secondary outputs + * that are relative to the build directory + */ + protected void calculateOutputsForSource(ITool tool, String relativePath, IResource resource, IPath sourceLocation, + Vector ruleOutputs, Vector enumeratedPrimaryOutputs, + Vector enumeratedSecondaryOutputs) { + String inExt = sourceLocation.getFileExtension(); + String outExt = tool.getOutputExtension(inExt); + IResourceInfo rcInfo = tool.getParentResourceInfo(); + + IOutputType[] outTypes = tool.getOutputTypes(); + if (outTypes != null && outTypes.length > 0) { + for (IOutputType type : outTypes) { + boolean primaryOutput = (type == tool.getPrimaryOutputType()); + //if (primaryOutput && ignorePrimary) continue; + String outputPrefix = type.getOutputPrefix(); + + // Resolve any macros in the outputPrefix + // Note that we cannot use file macros because if we do a clean + // we need to know the actual name of the file to clean, and + // cannot use any builder variables such as $@. Hence we use the + // next best thing, i.e. configuration context. + + // figure out the configuration we're using + // IBuildObject toolParent = tool.getParent(); + // IConfiguration config = null; + // if the parent is a config then we're done + // if (toolParent instanceof IConfiguration) + // config = (IConfiguration) toolParent; + // else if (toolParent instanceof IToolChain) { + // // must be a toolchain + // config = (IConfiguration) ((IToolChain) toolParent) + // .getParent(); + // } + // + // else if (toolParent instanceof IResourceConfiguration) { + // config = (IConfiguration) ((IResourceConfiguration) toolParent) + // .getParent(); + // } + + // else { + // // bad + // throw new AssertionError( + // "tool parent must be one of configuration, toolchain, or resource configuration"); + // } + + // if (config != null) { + + try { + + if (containsSpecialCharacters(sourceLocation.toString())) { + outputPrefix = ManagedBuildManager.getBuildMacroProvider().resolveValue(outputPrefix, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } else { + outputPrefix = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + outputPrefix, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } + } + + catch (BuildMacroException e) { + } + + // } + + boolean multOfType = type.getMultipleOfType(); + IOption option = tool.getOptionBySuperClassId(type.getOptionId()); + IManagedOutputNameProvider nameProvider = type.getNameProvider(); + String[] outputNames = type.getOutputNames(); + + // 1. If the tool is the build target and this is the primary output, + // use artifact name & extension + // Not appropriate here... + // 2. If an option is specified, use the value of the option + if (option != null) { + try { + List outputList = new ArrayList<>(); + int optType = option.getValueType(); + if (optType == IOption.STRING) { + outputList.add(outputPrefix + option.getStringValue()); + } else if (optType == IOption.STRING_LIST || optType == IOption.LIBRARIES + || optType == IOption.OBJECTS || optType == IOption.INCLUDE_FILES + || optType == IOption.LIBRARY_PATHS || optType == IOption.LIBRARY_FILES + || optType == IOption.MACRO_FILES) { + @SuppressWarnings("unchecked") + List value = (List) option.getValue(); + outputList = value; + ((Tool) tool).filterValues(optType, outputList); + // Add outputPrefix to each if necessary + if (outputPrefix.length() > 0) { + for (int j = 0; j < outputList.size(); j++) { + outputList.set(j, outputPrefix + outputList.get(j)); + } + } + } + for (int j = 0; j < outputList.size(); j++) { + String outputName = outputList.get(j); + + // try to resolve the build macros in the output + // names + try { + + String resolved = null; + + if (containsSpecialCharacters(sourceLocation.toString())) { + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValue(outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, null, option, tool)); + } + + else { + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, null, option, tool)); + } + + if ((resolved = resolved.trim()).length() > 0) + outputName = resolved; + } catch (BuildMacroException e) { + } + + IPath outPath = Path.fromOSString(outputName); + // If only a file name is specified, add the relative path of this output directory + if (outPath.segmentCount() == 1) { + outPath = Path.fromOSString(relativePath + outputList.get(j)); + } + if (primaryOutput) { + ruleOutputs.add(j, outPath); + enumeratedPrimaryOutputs.add(j, resolvePercent(outPath, sourceLocation)); + } else { + ruleOutputs.add(outPath); + enumeratedSecondaryOutputs.add(resolvePercent(outPath, sourceLocation)); + } + } + } catch (BuildException ex) { + } + } else + // 3. If a nameProvider is specified, call it + if (nameProvider != null) { + IPath[] inPaths = new IPath[1]; + inPaths[0] = sourceLocation; + IPath[] outPaths = nameProvider.getOutputNames(tool, inPaths); + if (outPaths != null) { + for (int j = 0; j < outPaths.length; j++) { + IPath outPath = outPaths[j]; + String outputName = outPaths[j].toString(); + + // try to resolve the build macros in the output names + try { + + String resolved = null; + + if (containsSpecialCharacters(sourceLocation.toString())) { + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValue(outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, null, option, tool)); + } + + else { + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, null, option, tool)); + } + + if ((resolved = resolved.trim()).length() > 0) + outputName = resolved; + } catch (BuildMacroException e) { + } + + // If only a file name is specified, add the relative path of this output directory + if (outPath.segmentCount() == 1) { + outPath = Path.fromOSString(relativePath + outPath.toString()); + } + if (primaryOutput) { + ruleOutputs.add(j, outPath); + enumeratedPrimaryOutputs.add(j, resolvePercent(outPath, sourceLocation)); + } else { + ruleOutputs.add(outPath); + enumeratedSecondaryOutputs.add(resolvePercent(outPath, sourceLocation)); + } + } + } + } else + // 4. If outputNames is specified, use it + if (outputNames != null) { + for (int j = 0; j < outputNames.length; j++) { + String outputName = outputNames[j]; + try { + //try to resolve the build macros in the output names + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_FILE, + new FileContextData(sourceLocation, null, option, tool)); + if ((resolved = resolved.trim()).length() > 0) + outputName = resolved; + } catch (BuildMacroException e) { + } + + IPath outPath = Path.fromOSString(outputName); + // If only a file name is specified, add the relative path of this output directory + if (outPath.segmentCount() == 1) { + outPath = Path.fromOSString(relativePath + outPath.toString()); + } + if (primaryOutput) { + ruleOutputs.add(j, outPath); + enumeratedPrimaryOutputs.add(j, resolvePercent(outPath, sourceLocation)); + } else { + ruleOutputs.add(outPath); + enumeratedSecondaryOutputs.add(resolvePercent(outPath, sourceLocation)); + } + } + } else { + // 5. Use the name pattern to generate a transformation macro + // so that the source names can be transformed into the target names + // using the built-in string substitution functions of make. + if (multOfType) { + // This case is not handled - a nameProvider or outputNames must be specified + // TODO - report error + } else { + String namePattern = type.getNamePattern(); + IPath namePatternPath = null; + if (namePattern == null || namePattern.length() == 0) { + namePattern = relativePath + outputPrefix + IManagedBuilderMakefileGenerator.WILDCARD; + if (outExt != null && outExt.length() > 0) { + namePattern += DOT + outExt; + } + namePatternPath = Path.fromOSString(namePattern); + } else { + if (outputPrefix.length() > 0) { + namePattern = outputPrefix + namePattern; + } + namePatternPath = Path.fromOSString(namePattern); + // If only a file name is specified, add the relative path of this output directory + if (namePatternPath.segmentCount() == 1) { + namePatternPath = Path.fromOSString(relativePath + namePatternPath.toString()); + } + } + + if (primaryOutput) { + ruleOutputs.add(0, namePatternPath); + enumeratedPrimaryOutputs.add(0, resolvePercent(namePatternPath, sourceLocation)); + } else { + ruleOutputs.add(namePatternPath); + enumeratedSecondaryOutputs.add(resolvePercent(namePatternPath, sourceLocation)); + } + } + } + } + } else { + // For support of pre-CDT 3.0 integrations. + // NOTE WELL: This only supports the case of a single "target tool" + // that consumes exactly all of the object files, $OBJS, produced + // by other tools in the build and produces a single output. + // In this case, the output file name is the input file name with + // the output extension. + + //if (!ignorePrimary) { + String outPrefix = tool.getOutputPrefix(); + IPath outPath = Path.fromOSString(relativePath + outPrefix + WILDCARD); + outPath = outPath.addFileExtension(outExt); + ruleOutputs.add(0, outPath); + enumeratedPrimaryOutputs.add(0, resolvePercent(outPath, sourceLocation)); + //} + } + } + + /** + * If the path contains a %, returns the path resolved using the resource name + * + */ + protected IPath resolvePercent(IPath outPath, IPath sourceLocation) { + // Get the input file name + String fileName = sourceLocation.removeFileExtension().lastSegment(); + // Replace the % with the file name + String outName = outPath.toOSString().replaceAll("%", fileName); //$NON-NLS-1$ + IPath result = Path.fromOSString(outName); + return DOT_SLASH_PATH.isPrefixOf(outPath) ? DOT_SLASH_PATH.append(result) : result; + } + + /** + * Returns the dependency IPaths for this invocation of the tool with the specified source file + * + * @param depGen the dependency calculator + * @param tool tool used to build the source file + * @param relativePath build output directory relative path of the current output directory + * @param resource source file to scan for dependencies + * @return Vector of IPaths that are relative to the build directory + */ + protected IPath[] oldCalculateDependenciesForSource(IManagedDependencyGenerator depGen, ITool tool, + String relativePath, IResource resource) { + Vector deps = new Vector<>(); + int type = depGen.getCalculatorType(); + + switch (type) { + + case IManagedDependencyGeneratorType.TYPE_INDEXER: + case IManagedDependencyGeneratorType.TYPE_EXTERNAL: + IResource[] res = depGen.findDependencies(resource, project); + if (res != null) { + for (IResource re : res) { + IPath dep = null; + if (re != null) { + IPath addlPath = re.getLocation(); + if (addlPath != null) { + dep = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), addlPath); + } + } + if (dep != null) { + deps.add(dep); + } + } + } + break; + + case IManagedDependencyGeneratorType.TYPE_NODEPS: + default: + break; + } + return deps.toArray(new IPath[deps.size()]); + } + + /** + * Returns the dependency IPaths relative to the build directory + * + * @param depCalculator the dependency calculator + * @return IPath[] that are relative to the build directory + */ + protected IPath[] calculateDependenciesForSource(IManagedDependencyCalculator depCalculator) { + IPath[] addlDeps = depCalculator.getDependencies(); + if (addlDeps != null) { + for (int i = 0; i < addlDeps.length; i++) { + if (!addlDeps[i].isAbsolute()) { + // Convert from project relative to build directory relative + IPath absolutePath = project.getLocation().append(addlDeps[i]); + addlDeps[i] = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), absolutePath); + } + } + } + return addlDeps; + } + + /************************************************************************* + * M A K E F I L E G E N E R A T I O N C O M M O N M E T H O D S + ************************************************************************/ + + /** + * Generates a source macro name from a file extension + */ + public StringBuffer getSourceMacroName(String extensionName) { + StringBuffer macroName = new StringBuffer(); + + // We need to handle case sensitivity in file extensions (e.g. .c vs .C), so if the + // extension was already upper case, tack on an "UPPER_" to the macro name. + // In theory this means there could be a conflict if you had for example, + // extensions .c_upper, and .C, but realistically speaking the chances of this are + // practically nil so it doesn't seem worth the hassle of generating a truly + // unique name. + if (extensionName.equals(extensionName.toUpperCase())) { + macroName.append(extensionName.toUpperCase()).append("_UPPER"); //$NON-NLS-1$ + } else { + // lower case... no need for "UPPER_" + macroName.append(extensionName.toUpperCase()); + } + macroName.append("_SRCS"); //$NON-NLS-1$ + return macroName; + } + + /** + * Generates a generated dependency file macro name from a file extension + */ + public StringBuffer getDepMacroName(String extensionName) { + StringBuffer macroName = new StringBuffer(); + + // We need to handle case sensitivity in file extensions (e.g. .c vs .C), so if the + // extension was already upper case, tack on an "UPPER_" to the macro name. + // In theory this means there could be a conflict if you had for example, + // extensions .c_upper, and .C, but realistically speaking the chances of this are + // practically nil so it doesn't seem worth the hassle of generating a truly + // unique name. + if (extensionName.equals(extensionName.toUpperCase())) { + macroName.append(extensionName.toUpperCase()).append("_UPPER"); //$NON-NLS-1$ + } else { + // lower case... no need for "UPPER_" + macroName.append(extensionName.toUpperCase()); + } + macroName.append("_DEPS"); //$NON-NLS-1$ + return macroName; + } + + /** + * Answers all of the output extensions that the target + * of the build has tools defined to work on. + * + * @return a Set containing all of the output extensions + */ + public Set getOutputExtensions(ToolInfoHolder h) { + if (h.outputExtensionsSet == null) { + // The set of output extensions which will be produced by this tool. + // It is presumed that this set is not very large (likely < 10) so + // a HashSet should provide good performance. + h.outputExtensionsSet = new HashSet<>(); + + // For each tool for the target, lookup the kinds of sources it outputs + // and add that to our list of output extensions. + for (ITool tool : h.buildTools) { + String[] outputs = tool.getAllOutputExtensions(); + if (outputs != null) { + h.outputExtensionsSet.addAll(Arrays.asList(outputs)); + } + } + } + return h.outputExtensionsSet; + } + + /** + * @deprecated Use {@link GnuMakefileGenerator#generateDummyTargets(IConfiguration, IFile, boolean)} + */ + @Deprecated + static public boolean populateDummyTargets(IConfiguration cfg, IFile makefile, boolean force) + throws CoreException, IOException { + return new GnuMakefileGenerator().generateDummyTargets(cfg, makefile, force); + } + + /** + * This method postprocesses a .d file created by a build. + * It's main job is to add dummy targets for the header files dependencies. + * This prevents make from aborting the build if the header file does not exist. + * + * A secondary job is to work in tandem with the "echo" command that is used + * by some tool-chains in order to get the "targets" part of the dependency rule + * correct. + * + * This method adds a comment to the beginning of the dependency file which it + * checks for to determine if this dependency file has already been updated. + * + * @return a true if the dependency file is modified + * @since 9.3 + */ + public boolean generateDummyTargets(IConfiguration cfg, IFile makefile, boolean force) + throws CoreException, IOException { + return generateDummyTargets(cfg.getRootFolderInfo(), makefile, force); + } + + /** + * @deprecated Use {@link GnuMakefileGenerator#generateDummyTargets(IResourceInfo, IFile, boolean)} + */ + @Deprecated + static public boolean populateDummyTargets(IResourceInfo rcInfo, IFile makefile, boolean force) + throws CoreException, IOException { + return new GnuMakefileGenerator().generateDummyTargets(rcInfo, makefile, force); + } + + /** + * @since 9.3 + */ + public boolean generateDummyTargets(IResourceInfo rcInfo, IFile makefile, boolean force) + throws CoreException, IOException { + + if (makefile == null || !makefile.exists()) + return false; + + // Get the contents of the dependency file + StringBuffer inBuffer; + InputStream contentStream = makefile.getContents(false); + try (Reader in = new InputStreamReader(contentStream)) { + int chunkSize = contentStream.available(); + inBuffer = new StringBuffer(chunkSize); + char[] readBuffer = new char[chunkSize]; + int n = in.read(readBuffer); + while (n > 0) { + inBuffer.append(readBuffer); + n = in.read(readBuffer); + } + } + + // The rest of this operation is equally expensive, so + // if we are doing an incremental build, only update the + // files that do not have a comment + String inBufferString = inBuffer.toString(); + if (!force && inBufferString.startsWith(COMMENT_SYMBOL)) { + return false; + } + + // Try to determine if this file already has dummy targets defined. + // If so, we will only add the comment. + String[] bufferLines = inBufferString.split("[\\r\\n]"); //$NON-NLS-1$ + for (String bufferLine : bufferLines) { + if (bufferLine.endsWith(":")) { //$NON-NLS-1$ + StringBuffer outBuffer = addDefaultHeader(); + outBuffer.append(inBuffer); + save(outBuffer, makefile); + return true; + } + } + + // Reconstruct the buffer tokens into useful chunks of dependency information + Vector bufferTokens = new Vector<>(Arrays.asList(inBufferString.split("\\s"))); //$NON-NLS-1$ + Vector deps = new Vector<>(bufferTokens.size()); + Iterator tokenIter = bufferTokens.iterator(); + while (tokenIter.hasNext()) { + String token = tokenIter.next(); + if (token.lastIndexOf("\\") == token.length() - 1 && token.length() > 1) { //$NON-NLS-1$ + // This is escaped so keep adding to the token until we find the end + while (tokenIter.hasNext()) { + String nextToken = tokenIter.next(); + token += WHITESPACE + nextToken; + if (!nextToken.endsWith("\\")) { //$NON-NLS-1$ + //$NON-NLS-1$ + break; + } + } + } + deps.add(token); + } + deps.trimToSize(); + + // Now find the header file dependencies and make dummy targets for them + boolean save = false; + StringBuffer outBuffer = null; + + // If we are doing an incremental build, only update the files that do not have a comment + String firstToken; + try { + firstToken = deps.get(0); + } catch (ArrayIndexOutOfBoundsException e) { + // This makes no sense so bail + return false; + } + + // Put the generated comments in the output buffer + if (!firstToken.startsWith(COMMENT_SYMBOL)) { + outBuffer = addDefaultHeader(); + } else { + outBuffer = new StringBuffer(); + } + + // Some echo implementations misbehave and put the -n and newline in the output + if (firstToken.startsWith("-n")) { //$NON-NLS-1$ + + // Now let's parse: + // Win32 outputs -n '/.d /' + // POSIX outputs -n /.d / + // Get the dep file name + String secondToken; + try { + secondToken = deps.get(1); + } catch (ArrayIndexOutOfBoundsException e) { + secondToken = ""; //$NON-NLS-1$ + } + if (secondToken.startsWith("'")) { //$NON-NLS-1$ + // This is the Win32 implementation of echo (MinGW without MSYS) + outBuffer.append(secondToken.substring(1)).append(WHITESPACE); + } else { + outBuffer.append(secondToken).append(WHITESPACE); + } + + // The relative path to the build goal comes next + String thirdToken; + try { + thirdToken = deps.get(2); + } catch (ArrayIndexOutOfBoundsException e) { + thirdToken = ""; //$NON-NLS-1$ + } + int lastIndex = thirdToken.lastIndexOf("'"); //$NON-NLS-1$ + if (lastIndex != -1) { + if (lastIndex == 0) { + outBuffer.append(WHITESPACE); + } else { + outBuffer.append(thirdToken.substring(0, lastIndex - 1)); + } + } else { + outBuffer.append(thirdToken); + } + + // Followed by the target output by the compiler plus ':' + // If we see any empty tokens here, assume they are the result of + // a line feed output by "echo" and skip them + String fourthToken; + int nToken = 3; + try { + do { + fourthToken = deps.get(nToken++); + } while (fourthToken.length() == 0); + + } catch (ArrayIndexOutOfBoundsException e) { + fourthToken = ""; //$NON-NLS-1$ + } + outBuffer.append(fourthToken).append(WHITESPACE); + + // Followed by the actual dependencies + try { + for (String nextElement : deps) { + if (nextElement.endsWith("\\")) { //$NON-NLS-1$ + outBuffer.append(nextElement).append(NEWLINE).append(WHITESPACE); + } else { + outBuffer.append(nextElement).append(WHITESPACE); + } + } + } catch (IndexOutOfBoundsException e) { + } + + } else { + outBuffer.append(inBuffer); + } + + outBuffer.append(NEWLINE); + save = true; + + IFolderInfo fo = null; + if (rcInfo instanceof IFolderInfo) { + fo = (IFolderInfo) rcInfo; + } else { + IConfiguration c = rcInfo.getParent(); + fo = (IFolderInfo) c.getResourceInfo(rcInfo.getPath().removeLastSegments(1), false); + } + // Dummy targets to add to the makefile + for (String dummy : deps) { + IPath dep = new Path(dummy); + String extension = dep.getFileExtension(); + if (fo.isHeaderFile(extension)) { + /* + * The formatting here is + * : + */ + outBuffer.append(dummy).append(COLON).append(NEWLINE).append(NEWLINE); + } + } + + // Write them out to the makefile + if (save) { + save(outBuffer, makefile); + return true; + } + + return false; + } + + /** + * prepend all instanced of '\' or '"' with a backslash + * + * @return resulting string + */ + static public String escapedEcho(String string) { + String escapedString = string.replaceAll("'", "'\"'\"'"); //$NON-NLS-1$ //$NON-NLS-2$ + return ECHO + WHITESPACE + SINGLE_QUOTE + escapedString + SINGLE_QUOTE + NEWLINE; + } + + static public String ECHO_BLANK_LINE = ECHO + WHITESPACE + SINGLE_QUOTE + WHITESPACE + SINGLE_QUOTE + NEWLINE; + + /** + * @deprecated Use {@link GnuMakefileGenerator#addGenericHeader()} + */ + @Deprecated + static protected StringBuffer addDefaultHeader() { + return new GnuMakefileGenerator().addGenericHeader(); + } + + /** + * Outputs a comment formatted as follows: + * ##### ....... ##### + * # + * ##### ....... ##### + * @since 9.3 + */ + protected StringBuffer addGenericHeader() { + StringBuffer buffer = new StringBuffer(); + outputCommentLine(buffer); + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(HEADER)) + .append(NEWLINE); + addCustomHeader(buffer); + outputCommentLine(buffer); + buffer.append(NEWLINE); + return buffer; + } + + /** + * @since 9.3 + */ + protected void addCustomHeader(StringBuffer buffer) { + } + + /** + * Put COLS_PER_LINE comment charaters in the argument. + */ + static protected void outputCommentLine(StringBuffer buffer) { + for (int i = 0; i < COLS_PER_LINE; i++) { + buffer.append(COMMENT_SYMBOL); + } + buffer.append(NEWLINE); + } + + static public boolean containsSpecialCharacters(String path) { + return path.matches(".*(\\s|[\\{\\}\\(\\)\\$\\@%=;]).*"); //$NON-NLS-1$ + } + + /** + * Answers the argument with all whitespaces replaced with an escape sequence. + */ + static public String escapeWhitespaces(String path) { + // Escape the spaces in the path/filename if it has any + String[] segments = path.split("\\s"); //$NON-NLS-1$ + if (segments.length > 1) { + StringBuffer escapedPath = new StringBuffer(); + for (int index = 0; index < segments.length; ++index) { + escapedPath.append(segments[index]); + if (index + 1 < segments.length) { + escapedPath.append("\\ "); //$NON-NLS-1$ + } + } + return escapedPath.toString().trim(); + } else { + return path; + } + } + + /** + * Adds a macro addition prefix to a map of macro names to entries. + * Entry prefixes look like: + * C_SRCS += \ + * ${addprefix $(ROOT)/, \ + */ + // TODO fix comment + protected void addMacroAdditionPrefix(LinkedHashMap map, String macroName, String relativePath, + boolean addPrefix) { + // there is no entry in the map, so create a buffer for this macro + StringBuffer tempBuffer = new StringBuffer(); + tempBuffer.append(macroName).append(WHITESPACE).append(MACRO_ADDITION_PREFIX_SUFFIX); + if (addPrefix) { + tempBuffer.append(MACRO_ADDITION_ADDPREFIX_HEADER).append(relativePath) + .append(MACRO_ADDITION_ADDPREFIX_SUFFIX); + } + + // have to store the buffer in String form as StringBuffer is not a sublcass of Object + map.put(macroName, tempBuffer.toString()); + } + + /** + * Adds a file to an entry in a map of macro names to entries. + * File additions look like: + * example.c, \ + */ + protected void addMacroAdditionFile(HashMap map, String macroName, String filename) { + StringBuffer buffer = new StringBuffer(); + buffer.append(map.get(macroName)); + + // escape whitespace in the filename + filename = escapeWhitespaces(filename); + + buffer.append(filename).append(WHITESPACE).append(LINEBREAK); + // re-insert string in the map + map.put(macroName, buffer.toString()); + } + + /** + * Adds a file to an entry in a map of macro names to entries. + * File additions look like: + * example.c, \ + */ + protected void addMacroAdditionFile(HashMap map, String macroName, String relativePath, + IPath sourceLocation, boolean generatedSource) { + // Add the source file path to the makefile line that adds source files to the build variable + String srcName; + IPath projectLocation = getPathForResource(project); + IPath dirLocation = projectLocation; + if (generatedSource) { + dirLocation = dirLocation.append(getBuildWorkingDir()); + } + if (dirLocation.isPrefixOf(sourceLocation)) { + IPath srcPath = sourceLocation.removeFirstSegments(dirLocation.segmentCount()).setDevice(null); + if (generatedSource) { + srcName = "./" + srcPath.toString(); //$NON-NLS-1$ + } else { + srcName = reachProjectRoot() + SEPARATOR + srcPath.toString(); + } + } else { + if (generatedSource && !sourceLocation.isAbsolute()) { + srcName = "./" + relativePath + sourceLocation.lastSegment().toString(); //$NON-NLS-1$ + } else { + // TODO: Should we use relative paths when possible (e.g., see MbsMacroSupplier.calculateRelPath) + srcName = sourceLocation.toString(); + } + } + addMacroAdditionFile(map, macroName, srcName); + } + + /** + * Adds file(s) to an entry in a map of macro names to entries. + * File additions look like: + * example.c, \ + */ + public void addMacroAdditionFiles(HashMap map, String macroName, Vector filenames) { + StringBuffer buffer = new StringBuffer(); + buffer.append(map.get(macroName)); + for (int i = 0; i < filenames.size(); i++) { + String filename = filenames.get(i); + if (filename.length() > 0) { + + // Bug 417288, ilg@livius.net & freidin.alex@gmail.com + filename = ensurePathIsGNUMakeTargetRuleCompatibleSyntax(filename); + + buffer.append(filename).append(WHITESPACE).append(LINEBREAK); + } + } + // re-insert string in the map + map.put(macroName, buffer.toString()); + } + + /** + * Write all macro addition entries in a map to the buffer + */ + protected StringBuffer writeAdditionMacros(LinkedHashMap map) { + StringBuffer buffer = new StringBuffer(); + // Add the comment + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(MOD_VARS)) + .append(NEWLINE); + + for (String macroString : map.values()) { + // Check if we added any files to the rule + // Currently, we do this by comparing the end of the rule buffer to MACRO_ADDITION_PREFIX_SUFFIX + if (!(macroString.endsWith(MACRO_ADDITION_PREFIX_SUFFIX))) { + StringBuffer currentBuffer = new StringBuffer(); + + // Remove the final "/" + if (macroString.endsWith(LINEBREAK)) { + macroString = macroString.substring(0, (macroString.length() - 2)) + NEWLINE; + } + currentBuffer.append(macroString); + + currentBuffer.append(NEWLINE); + + // append the contents of the buffer to the master buffer for + // the whole file + buffer.append(currentBuffer); + } + } + return buffer.append(NEWLINE); + } + + /** + * Write all macro addition entries in a map to the buffer + */ + protected StringBuffer writeTopAdditionMacros(List varList, HashMap varMap) { + StringBuffer buffer = new StringBuffer(); + // Add the comment + buffer.append(COMMENT_SYMBOL).append(WHITESPACE).append(ManagedMakeMessages.getResourceString(MOD_VARS)) + .append(NEWLINE); + + for (int i = 0; i < varList.size(); i++) { + String addition = varMap.get(varList.get(i)); + StringBuffer currentBuffer = new StringBuffer(); + currentBuffer.append(addition); + currentBuffer.append(NEWLINE); + + // append the contents of the buffer to the master buffer for the whole file + buffer.append(currentBuffer); + } + return buffer.append(NEWLINE); + } + + /** + * Calculates the inputs and outputs for tools that will be generated in the top makefile. + * This information is used by the top level makefile generation methods. + */ + protected void calculateToolInputsOutputs() { + + toolInfos.accept(new IPathSettingsContainerVisitor() { + @Override + public boolean visit(PathSettingsContainer container) { + ToolInfoHolder h = (ToolInfoHolder) container.getValue(); + ITool[] buildTools = h.buildTools; + ManagedBuildGnuToolInfo[] gnuToolInfos = h.gnuToolInfos; + // We are "done" when the information for all tools has been calculated, + // or we are not making any progress + boolean done = false; + boolean lastChance = false; + int[] doneState = new int[buildTools.length]; + + // Identify the target tool + ITool targetTool = config.calculateTargetTool(); + // if (targetTool == null) { + // targetTool = info.getToolFromOutputExtension(buildTargetExt); + // } + + // Initialize the tool info array and the done state + + if (buildTools.length != 0 && buildTools[0].getCustomBuildStep()) + return true; + + for (int i = 0; i < buildTools.length; i++) { + if ((buildTools[i] == targetTool)) { + String ext = config.getArtifactExtension(); + //try to resolve the build macros in the artifact extension + try { + ext = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(ext, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } catch (BuildMacroException e) { + } + + String name = config.getArtifactName(); + //try to resolve the build macros in the artifact name + try { + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + name, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + if ((resolved = resolved.trim()).length() > 0) + name = resolved; + } catch (BuildMacroException e) { + } + + gnuToolInfos[i] = new ManagedBuildGnuToolInfo(project, buildTools[i], true, name, ext); + } else { + gnuToolInfos[i] = new ManagedBuildGnuToolInfo(project, buildTools[i], false, null, null); + } + doneState[i] = 0; + } + + // Initialize the build output variable to file additions map + LinkedHashMap map = getTopBuildOutputVars(); + Set>> set = buildOutVars.entrySet(); + for (Entry> entry : set) { + String macroName = entry.getKey(); + + // for projects with specific setting on folders/files do + // not clear the macro value on subsequent passes + if (!map.containsKey(macroName)) { + addMacroAdditionPrefix(map, macroName, "", false); //$NON-NLS-1$ + } + } + + // Set of input extensions for which macros have been created so far + HashSet handledDepsInputExtensions = new HashSet<>(); + HashSet handledOutsInputExtensions = new HashSet<>(); + + while (!done) { + int[] testState = new int[doneState.length]; + for (int i = 0; i < testState.length; i++) + testState[i] = 0; + + // Calculate inputs + for (int i = 0; i < gnuToolInfos.length; i++) { + if (gnuToolInfos[i].areInputsCalculated()) { + testState[i]++; + } else { + if (gnuToolInfos[i].calculateInputs(GnuMakefileGenerator.this, config, projectResources, h, + lastChance)) { + testState[i]++; + } + } + } + // Calculate dependencies + for (int i = 0; i < gnuToolInfos.length; i++) { + if (gnuToolInfos[i].areDependenciesCalculated()) { + testState[i]++; + } else { + if (gnuToolInfos[i].calculateDependencies(GnuMakefileGenerator.this, config, + handledDepsInputExtensions, h, lastChance)) { + testState[i]++; + } + } + } + // Calculate outputs + for (int i = 0; i < gnuToolInfos.length; i++) { + if (gnuToolInfos[i].areOutputsCalculated()) { + testState[i]++; + } else { + if (gnuToolInfos[i].calculateOutputs(GnuMakefileGenerator.this, config, + handledOutsInputExtensions, lastChance)) { + testState[i]++; + } + } + } + // Are all calculated? If so, done. + done = true; + for (int element : testState) { + if (element != 3) { + done = false; + break; + } + } + + // Test our "done" state vs. the previous "done" state. + // If we have made no progress, give it a "last chance" and then quit + if (!done) { + done = true; + for (int i = 0; i < testState.length; i++) { + if (testState[i] != doneState[i]) { + done = false; + break; + } + } + } + if (done) { + if (!lastChance) { + lastChance = true; + done = false; + } + } + if (!done) { + doneState = testState; + } + } + return true; + } + }); + } + + /** + * Returns the (String) list of files associated with the build variable + * + * @param variable the variable name + * @param locationType the format in which we want the filenames returned + * @param directory project relative directory path used with locationType == DIRECTORY_RELATIVE + * @param getAll only return the list if all tools that are going to contrubute to this + * variable have done so. + * @return List + */ + public List getBuildVariableList(ToolInfoHolder h, String variable, int locationType, IPath directory, + boolean getAll) { + ManagedBuildGnuToolInfo[] gnuToolInfos = h.gnuToolInfos; + boolean done = true; + for (int i = 0; i < gnuToolInfos.length; i++) { + if (!gnuToolInfos[i].areOutputVariablesCalculated()) { + done = false; + } + } + if (!done && getAll) + return null; + List list = buildSrcVars.get(variable); + if (list == null) { + list = buildOutVars.get(variable); + } + + List fileList = null; + if (list != null) { + // Convert the items in the list to the location-type wanted by the caller, + // and to a string list + IPath dirLocation = null; + if (locationType != ABSOLUTE) { + dirLocation = project.getLocation(); + if (locationType == PROJECT_SUBDIR_RELATIVE) { + dirLocation = dirLocation.append(directory); + } + } + for (int i = 0; i < list.size(); i++) { + IPath path = list.get(i); + if (locationType != ABSOLUTE) { + if (dirLocation != null && dirLocation.isPrefixOf(path)) { + path = path.removeFirstSegments(dirLocation.segmentCount()).setDevice(null); + } + } + if (fileList == null) { + fileList = new Vector<>(); + } + fileList.add(path.toString()); + } + } + + return fileList; + } + + /** + * Returns the map of build variables to list of files + * + * @return HashMap + */ + public HashMap> getBuildOutputVars() { + return buildOutVars; + } + + /** + * Returns the map of build variables used in the top makefile to list of files + * + * @return HashMap + */ + public LinkedHashMap getTopBuildOutputVars() { + return topBuildOutVars; + } + + /** + * Returns the list of known build rules. This keeps me from generating duplicate + * rules for known file extensions. + * + * @return List + */ + protected Vector getRuleList() { + if (ruleList == null) { + ruleList = new Vector<>(); + } + return ruleList; + } + + /** + * Returns the list of known dependency lines. This keeps me from generating duplicate + * lines. + * + * @return List + */ + protected Vector getDepLineList() { + if (depLineList == null) { + depLineList = new Vector<>(); + } + return depLineList; + } + + /** + * Returns the list of known dependency file generation lines. This keeps me from + * generating duplicate lines. + * + * @return List + */ + protected Vector getDepRuleList() { + if (depRuleList == null) { + depRuleList = new Vector<>(); + } + return depRuleList; + } + + /************************************************************************* + * R E S O U R C E V I S I T O R M E T H O D S + ************************************************************************/ + + /** + * 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. + */ + protected void appendBuildSubdirectory(IResource resource) { + IContainer container = resource.getParent(); + // Only add the container once + if (!getSubdirList().contains(container)) + getSubdirList().add(container); + } + + /** + * Adds the container of the argument to a list of subdirectories that are to be + * deleted. As a result, the directories that are generated for the output + * should be removed as well. + */ + protected void appendDeletedSubdirectory(IContainer container) { + // No point in adding a folder if the parent is already there + IContainer parent = container.getParent(); + if (!getDeletedDirList().contains(container) && !getDeletedDirList().contains(parent)) { + getDeletedDirList().add(container); + } + } + + /** + * If a file is removed from a source folder (either because of a delete + * or move action on the part of the user), the makefilegenerator has to + * remove the dependency makefile along with the old build goal + */ + protected void appendDeletedFile(IResource resource) { + // Cache this for now + getDeletedFileList().add(resource); + } + + /** + * 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. + */ + protected void appendModifiedSubdirectory(IResource resource) { + IContainer container = resource.getParent(); + + if (!getModifiedList().contains(container)) { + getModifiedList().add(container); + } + } + + /************************************************************************* + * O T H E R M E T H O D S + ************************************************************************/ + + protected void cancel(String message) { + if (monitor != null && !monitor.isCanceled()) { + throw new OperationCanceledException(message); + } + } + + /** + * Check whether the build has been cancelled. Cancellation requests + * propagated to the caller by throwing OperationCanceledException. + * + * @see org.eclipse.core.runtime.OperationCanceledException#OperationCanceledException() + */ + protected void checkCancel() { + if (monitor != null && monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + + /** + * Return or create the folder needed for the build output. If we are + * creating the folder, set the derived bit to true so the CM system + * ignores the contents. If the resource exists, respect the existing + * derived setting. + */ + 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; + } + + // Make sure the folder is marked as derived so it is not added to CM + if (!folder.isDerived()) { + folder.setDerived(true, null); + } + } + + return folder.getFullPath(); + } + + /** + * Return or create the makefile needed for the build. If we are creating + * the resource, set the derived bit to true so the CM system ignores + * the contents. If the resource exists, respect the existing derived + * setting. + */ + 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, new SubProgressMonitor(monitor, 1)); + // Make sure the new file is marked as derived + if (!newFile.isDerived()) { + newFile.setDerived(true, null); + } + + } 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; + } + + private void deleteBuildTarget(IResource deletedFile) { + // Get the project relative path of the file + String fileName = getFileName(deletedFile); + String srcExtension = deletedFile.getFileExtension(); + IPath folderPath = inFullPathFromOutFullPath(deletedFile.getFullPath().removeLastSegments(1)); + if (folderPath != null) { + folderPath = folderPath.removeFirstSegments(1); + } else { + folderPath = new Path(""); //$NON-NLS-1$ + } + IResourceInfo rcInfo = config.getResourceInfo(folderPath, false); + if (rcInfo instanceof IFileInfo) { + rcInfo = config.getResourceInfo(folderPath.removeLastSegments(1), false); + } + String targetExtension = ((IFolderInfo) rcInfo).getOutputExtension(srcExtension); + if (!targetExtension.isEmpty()) + fileName += DOT + targetExtension; + IPath projectRelativePath = deletedFile.getProjectRelativePath().removeLastSegments(1); + IPath targetFilePath = getBuildWorkingDir().append(projectRelativePath).append(fileName); + IResource depFile = project.findMember(targetFilePath); + if (depFile != null && depFile.exists()) { + try { + depFile.delete(true, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + // This had better be allowed during a build + + } + } + } + + private IPath inFullPathFromOutFullPath(IPath path) { + IPath inPath = null; + if (topBuildDir.isPrefixOf(path)) { + inPath = path.removeFirstSegments(topBuildDir.segmentCount()); + inPath = project.getFullPath().append(path); + } + return inPath; + } + + private void deleteDepFile(IResource deletedFile) { + // Calculate the top build directory relative paths of the dependency files + IPath[] depFilePaths = null; + ITool tool = null; + IManagedDependencyGeneratorType depType = null; + String sourceExtension = deletedFile.getFileExtension(); + IPath folderPath = inFullPathFromOutFullPath(deletedFile.getFullPath().removeLastSegments(1)); + if (folderPath != null) { + folderPath = folderPath.removeFirstSegments(1); + } else { + folderPath = new Path(""); //$NON-NLS-1$ + } + ToolInfoHolder h = getToolInfo(folderPath); + ITool[] tools = h.buildTools; + for (int index = 0; index < tools.length; ++index) { + if (tools[index].buildsFileType(sourceExtension)) { + tool = tools[index]; + depType = tool.getDependencyGeneratorForExtension(sourceExtension); + break; + } + } + if (depType != null) { + int calcType = depType.getCalculatorType(); + if (calcType == IManagedDependencyGeneratorType.TYPE_COMMAND) { + depFilePaths = new IPath[1]; + IPath absolutePath = deletedFile.getLocation(); + depFilePaths[0] = ManagedBuildManager.calculateRelativePath(getTopBuildDir(), absolutePath); + depFilePaths[0] = depFilePaths[0].removeFileExtension().addFileExtension(DEP_EXT); + } else if (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS + || calcType == IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS) { + IManagedDependencyGenerator2 depGen = (IManagedDependencyGenerator2) depType; + IManagedDependencyInfo depInfo = depGen.getDependencySourceInfo(deletedFile.getProjectRelativePath(), + deletedFile, config, tool, getBuildWorkingDir()); + if (depInfo != null) { + if (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS) { + IManagedDependencyCommands depCommands = (IManagedDependencyCommands) depInfo; + depFilePaths = depCommands.getDependencyFiles(); + } else if (calcType == IManagedDependencyGeneratorType.TYPE_PREBUILD_COMMANDS) { + IManagedDependencyPreBuild depPreBuild = (IManagedDependencyPreBuild) depInfo; + depFilePaths = depPreBuild.getDependencyFiles(); + } + } + } + } + + // Delete the files if they exist + if (depFilePaths != null) { + for (IPath dfp : depFilePaths) { + IPath depFilePath = getBuildWorkingDir().append(dfp); + IResource depFile = project.findMember(depFilePath); + if (depFile != null && depFile.exists()) { + try { + depFile.delete(true, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + // This had better be allowed during a build + } + } + } + } + } + + /** + * Returns the current build configuration. + * + * @return String + * @since 8.0 + */ + protected IConfiguration getConfig() { + return config; + } + + /** + * Returns the build target extension. + * + * @return String + * @since 8.0 + */ + protected String getBuildTargetExt() { + return buildTargetExt; + } + + /** + * Returns the build target name. + * + * @return String + * @since 8.0 + */ + protected String getBuildTargetName() { + return buildTargetName; + } + + /** + * @return Returns the deletedDirList. + */ + private Vector getDeletedDirList() { + if (deletedDirList == null) { + deletedDirList = new Vector<>(); + } + return deletedDirList; + } + + private Vector getDeletedFileList() { + if (deletedFileList == null) { + deletedFileList = new Vector<>(); + } + return deletedFileList; + } + + private List getDependencyMakefiles(ToolInfoHolder h) { + if (h.dependencyMakefiles == null) { + h.dependencyMakefiles = new ArrayList<>(); + } + return h.dependencyMakefiles; + } + + /** + * Strips off the file extension from the argument and returns + * the name component in a String + */ + private String getFileName(IResource file) { + String answer = ""; //$NON-NLS-1$ + String lastSegment = file.getName(); + int extensionSeparator = lastSegment.lastIndexOf(DOT); + if (extensionSeparator != -1) { + answer = lastSegment.substring(0, extensionSeparator); + } + return answer; + } + + /** + * Answers a Vector containing a list of directories that are invalid for the + * build for some reason. At the moment, the only reason a directory would + * not be considered for the build is if it contains a space in the relative + * path from the project root. + * + * @return a a list of directories that are invalid for the build + */ + private Vector getInvalidDirList() { + if (invalidDirList == null) { + invalidDirList = new Vector<>(); + } + return invalidDirList; + } + + /** + * @return Collection of Containers which contain modified source files + */ + private Collection getModifiedList() { + if (modifiedList == null) + modifiedList = new LinkedHashSet<>(); + return modifiedList; + } + + /** + * @return Collection of subdirectories (IContainers) contributing source code to the build + */ + private Collection getSubdirList() { + if (subdirList == null) + subdirList = new LinkedHashSet<>(); + return subdirList; + } + + private void removeGeneratedDirectory(IContainer subDir) { + try { + // The source directory isn't empty + if (subDir.exists() && subDir.members().length > 0) + return; + } catch (CoreException e) { + // The resource doesn't exist so we should delete the output folder + } + + // Figure out what the generated directory name is and delete it + IPath moduleRelativePath = subDir.getProjectRelativePath(); + IPath buildRoot = getBuildWorkingDir(); + if (buildRoot == null) { + return; + } + IPath moduleOutputPath = buildRoot.append(moduleRelativePath); + IFolder folder = project.getFolder(moduleOutputPath); + if (folder.exists()) { + try { + folder.delete(true, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + // TODO Log this + } + } + } + + protected void updateMonitor(String msg) { + if (monitor != null && !monitor.isCanceled()) { + monitor.subTask(msg); + monitor.worked(1); + } + } + + /** + * Return the configuration's top build directory as an absolute path + */ + public IPath getTopBuildDir() { + return getPathForResource(project).append(getBuildWorkingDir()); + } + + /** + * Process a String denoting a filepath in a way compatible for GNU Make rules, handling + * windows drive letters and whitespace appropriately. + *

+ * The context these paths appear in is on the right hand side of a rule header. i.e. + *

+ * target : dep1 dep2 dep3 + *

+ * @param path the String denoting the path to process + * @throws NullPointerException is path is null + * @return a suitable Make rule compatible path + */ + /* see https://bugs.eclipse.org/bugs/show_bug.cgi?id=129782 */ + public String ensurePathIsGNUMakeTargetRuleCompatibleSyntax(String path) { + boolean isQuotedOption = false; + if (path.startsWith("-")) { //$NON-NLS-1$ + isQuotedOption = checkIfQuotedOption(path); + } + if (!isQuotedOption) + return escapeWhitespaces(ensureUnquoted(path)); + return path; + } + + private boolean checkIfQuotedOption(String path) { + Matcher m1 = doubleQuotedOption.matcher(path); + if (m1.matches()) + return true; + Matcher m2 = singleQuotedOption.matcher(path); + if (m2.matches()) + return true; + return false; + } + + /** + * Strips outermost quotes of Strings of the form "a" and 'a' or returns the original + * string if the input is not of this form. + * + * @throws NullPointerException if path is null + * @return a String without the outermost quotes (if the input has them) + */ + public static String ensureUnquoted(String path) { + boolean doubleQuoted = path.startsWith("\"") && path.endsWith("\""); //$NON-NLS-1$ //$NON-NLS-2$ + boolean singleQuoted = path.startsWith("'") && path.endsWith("'"); //$NON-NLS-1$ //$NON-NLS-2$ + return doubleQuoted || singleQuoted ? path.substring(1, path.length() - 1) : path; + } + + @Override + public void initialize(int buildKind, IConfiguration cfg, IBuilder builder, IProgressMonitor monitor) { + // Save the project so we can get path and member information + this.project = cfg.getOwner().getProject(); + if (builder == null) { + builder = cfg.getEditableBuilder(); + } + try { + projectResources = project.members(); + } catch (CoreException e) { + projectResources = null; + } + // 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 + buildTargetName = cfg.getArtifactName(); + // Get its extension + buildTargetExt = cfg.getArtifactExtension(); + + try { + //try to resolve the build macros in the target extension + buildTargetExt = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(buildTargetExt, + "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, builder); + } catch (BuildMacroException e) { + } + + try { + //try to resolve the build macros in the target name + String resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat(buildTargetName, + "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, builder); + if (resolved != null) { + resolved = resolved.trim(); + if (resolved.length() > 0) + buildTargetName = resolved; + } + } catch (BuildMacroException e) { + } + + if (buildTargetExt == null) { + buildTargetExt = ""; //$NON-NLS-1$ + } + // Cache the build tools + config = cfg; + this.builder = builder; + + initToolInfos(); + //set the top build dir path + initializeTopBuildDir(cfg.getName()); + + srcEntries = config.getSourceEntries(); + if (srcEntries.length == 0) { + srcEntries = new ICSourceEntry[] { + new CSourceEntry(Path.EMPTY, null, ICSettingEntry.RESOLVED | ICSettingEntry.VALUE_WORKSPACE_PATH) }; + } else { + ICConfigurationDescription cfgDes = ManagedBuildManager.getDescriptionForConfiguration(config); + srcEntries = CDataUtil.resolveEntries(srcEntries, cfgDes); + } + } + + private void initToolInfos() { + toolInfos = PathSettingsContainer.createRootContainer(); + + IResourceInfo rcInfos[] = config.getResourceInfos(); + for (IResourceInfo rcInfo : rcInfos) { + if (rcInfo.isExcluded() /*&& !((ResourceInfo)rcInfo).isRoot()*/) + continue; + + ToolInfoHolder h = getToolInfo(rcInfo.getPath(), true); + if (rcInfo instanceof IFolderInfo) { + IFolderInfo fo = (IFolderInfo) rcInfo; + h.buildTools = fo.getFilteredTools(); + h.buildToolsUsed = new boolean[h.buildTools.length]; + h.gnuToolInfos = new ManagedBuildGnuToolInfo[h.buildTools.length]; + } else { + IFileInfo fi = (IFileInfo) rcInfo; + h.buildTools = fi.getToolsToInvoke(); + h.buildToolsUsed = new boolean[h.buildTools.length]; + h.gnuToolInfos = new ManagedBuildGnuToolInfo[h.buildTools.length]; + } + } + } + + private ToolInfoHolder getToolInfo(IPath path) { + return getToolInfo(path, false); + } + + private ToolInfoHolder getFolderToolInfo(IPath path) { + IResourceInfo rcInfo = config.getResourceInfo(path, false); + while (rcInfo instanceof IFileInfo) { + path = path.removeLastSegments(1); + rcInfo = config.getResourceInfo(path, false); + } + return getToolInfo(path, false); + } + + private ToolInfoHolder getToolInfo(IPath path, boolean create) { + PathSettingsContainer child = toolInfos.getChildContainer(path, create, create); + ToolInfoHolder h = null; + if (child != null) { + h = (ToolInfoHolder) child.getValue(); + if (h == null && create) { + h = new ToolInfoHolder(); + child.setValue(h); + } + } + return h; + } + + private void ensureTopBuildDir() throws CoreException { + IPath buildWorkingDir = getBuildWorkingDir(); + if (buildWorkingDir != null) { + createDirectory(buildWorkingDir.toString()); + } + } + + private void initializeTopBuildDir(String configName) { + topBuildDir = project.getFolder(computeTopBuildDir(configName)).getFullPath(); + } + + /** + * Can be overwritten by a subclass to specify the top build directory to be + * used. Default implementation simply returns configuration name. + *

+ * Note: be careful by overriding this method - all places in the custom code and related + * scripts using or referencing top build directory must also be changed to use same logic. + * + * @param configName name of the configuration + * @return project relative path for top build directory + * @since 8.7 + */ + protected IPath computeTopBuildDir(String configName) { + return new Path(configName); + } + + /** + * @return As many ".." as required to get from getBuildWorkingDir() to the project root. + * + * E.g. If getBuildWorkingDir() is "Debug", then the function returns "..". If + * getBuildWorkingDir() returns "x86/Debug" then "../.." is returned. + * + * @since 8.7 + */ + public String reachProjectRoot() { + IPath buildWorkingDir = getBuildWorkingDir(); + if (buildWorkingDir == null) { + return ROOT; + } + String root = ROOT; + int segCnt = buildWorkingDir.segmentCount(); + for (int i = 1; i < segCnt; i++) { + root += SEPARATOR + ROOT; + } + return root; + } + +} diff --git a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/IManagedBuildGnuToolInfo.java b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/IManagedBuildGnuToolInfo.java new file mode 100644 index 00000000000..f95f7a6ef40 --- /dev/null +++ b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/IManagedBuildGnuToolInfo.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Intel Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Intel Corporation - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.managedbuilder.makegen.gnu2; + +import java.util.Vector; + +/** + * This interface returns information about a Tool's inputs + * and outputs while a Gnu makefile is being generated. + * + * @noextend This class is not intended to be subclassed by clients. + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IManagedBuildGnuToolInfo { + public final String DOT = "."; //$NON-NLS-1$ + + /** + * Returns true if the tool's inputs have been calculated, + * else false. + * + * @return boolean + */ + public boolean areInputsCalculated(); + + /** + * Returns the tool's inputs in command line format. This will use + * variables rather than actual file names as appropriate. + * + * @return Vector + */ + public Vector getCommandInputs(); + + /** + * Returns the raw list of tool's input file names. + * + * @return Vector + */ + public Vector getEnumeratedInputs(); + + /** + * Returns true if the tool's outputs have been calculated, + * else false. + * + * @return boolean + */ + public boolean areOutputsCalculated(); + + /** + * Returns the tool's outputs in command line format. This will use + * variables rather than actual file names as appropriate. + * + * @return Vector + */ + public Vector getCommandOutputs(); + + /** + * Returns the raw list of tool's primary output file names. + * + * @return Vector + */ + public Vector getEnumeratedPrimaryOutputs(); + + /** + * Returns the raw list of tool's secondary output file names. + * + * @return Vector + */ + public Vector getEnumeratedSecondaryOutputs(); + + /** + * Returns the raw list of tool's output variable names. + * + * @return Vector + */ + public Vector getOutputVariables(); + + /** + * Returns true if the tool's dependencies have been calculated, + * else false. + * + * @return boolean + */ + public boolean areDependenciesCalculated(); + + /** + * Returns the tool's dependencies in command line format. This will use + * variables rather than actual file names as appropriate. + * Dependencies are top build directory relative. + * + * @return Vector + */ + public Vector getCommandDependencies(); + + /** + * Returns the tool's additional targets as determined by the + * dependency calculator. + * Additional targets are top build directory relative + * + * @return Vector + */ + public Vector getAdditionalTargets(); + + /** + * Returns the raw list of tool's input dependencies. + * + * @return Vector + */ + //public Vector getEnumeratedDependencies(); + + /** + * Returns true if this is the target tool + * else false. + * + * @return boolean + */ + public boolean isTargetTool(); +} diff --git a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/ManagedBuildGnuToolInfo.java b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/ManagedBuildGnuToolInfo.java new file mode 100644 index 00000000000..30c6bfd06bb --- /dev/null +++ b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/makegen/gnu2/ManagedBuildGnuToolInfo.java @@ -0,0 +1,1004 @@ +/******************************************************************************* + * Copyright (c) 2005, 2016 Intel Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Intel Corporation - Initial API and implementation + * IBM Corporation + *******************************************************************************/ +package org.eclipse.cdt.managedbuilder.makegen.gnu2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Vector; + +import org.eclipse.cdt.managedbuilder.core.BuildException; +import org.eclipse.cdt.managedbuilder.core.IAdditionalInput; +import org.eclipse.cdt.managedbuilder.core.IConfiguration; +import org.eclipse.cdt.managedbuilder.core.IInputType; +import org.eclipse.cdt.managedbuilder.core.IManagedOutputNameProvider; +import org.eclipse.cdt.managedbuilder.core.IOption; +import org.eclipse.cdt.managedbuilder.core.IOutputType; +import org.eclipse.cdt.managedbuilder.core.ITool; +import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; +import org.eclipse.cdt.managedbuilder.internal.core.ManagedMakeMessages; +import org.eclipse.cdt.managedbuilder.internal.core.Tool; +import org.eclipse.cdt.managedbuilder.internal.macros.OptionContextData; +import org.eclipse.cdt.managedbuilder.macros.BuildMacroException; +import org.eclipse.cdt.managedbuilder.macros.IBuildMacroProvider; +import org.eclipse.cdt.managedbuilder.makegen.IManagedBuilderMakefileGenerator; +import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyCalculator; +import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyGenerator; +import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyGenerator2; +import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyGeneratorType; +import org.eclipse.cdt.managedbuilder.makegen.IManagedDependencyInfo; +import org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator.ToolInfoHolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + * This class represents information about a Tool's inputs + * and outputs while a Gnu makefile is being generated. + * + * @noextend This class is not intended to be subclassed by clients. + */ +public class ManagedBuildGnuToolInfo implements IManagedBuildGnuToolInfo { + + /* + * Members + */ + private IProject project; + private Tool tool; + private boolean bIsTargetTool; + private String targetName; + private String targetExt; + private boolean inputsCalculated = false; + private boolean outputsCalculated = false; + private boolean outputVariablesCalculated = false; + private boolean dependenciesCalculated = false; + private Vector commandInputs = new Vector<>(); + private Vector enumeratedInputs = new Vector<>(); + private Vector commandOutputs = new Vector<>(); + private Vector enumeratedPrimaryOutputs = new Vector<>(); + private Vector enumeratedSecondaryOutputs = new Vector<>(); + private Vector outputVariables = new Vector<>(); + private Vector commandDependencies = new Vector<>(); + private Vector additionalTargets = new Vector<>(); + //private Vector enumeratedDependencies = new Vector(); + // Map of macro names (String) to values (List) + + /* + * Constructor + */ + public ManagedBuildGnuToolInfo(IProject project, ITool tool, boolean targetTool, String name, String ext) { + this.project = project; + this.tool = (Tool) tool; + bIsTargetTool = targetTool; + if (bIsTargetTool) { + targetName = name; + targetExt = ext; + } + } + + /* + * IManagedBuildGnuToolInfo Methods + */ + @Override + public boolean areInputsCalculated() { + return inputsCalculated; + } + + // Command inputs are top build directory relative + @Override + public Vector getCommandInputs() { + return commandInputs; + } + + // Enumerated inputs are project relative + @Override + public Vector getEnumeratedInputs() { + return enumeratedInputs; + } + + @Override + public boolean areOutputsCalculated() { + return outputsCalculated; + } + + // Command outputs are top build directory relative + @Override + public Vector getCommandOutputs() { + return commandOutputs; + } + + @Override + public Vector getEnumeratedPrimaryOutputs() { + return enumeratedPrimaryOutputs; + } + + @Override + public Vector getEnumeratedSecondaryOutputs() { + return enumeratedSecondaryOutputs; + } + + @Override + public Vector getOutputVariables() { + return outputVariables; + } + + public boolean areOutputVariablesCalculated() { + return outputVariablesCalculated; + } + + @Override + public boolean areDependenciesCalculated() { + return dependenciesCalculated; + } + + // Command dependencies are top build directory relative + @Override + public Vector getCommandDependencies() { + return commandDependencies; + } + + // Additional targets are top build directory relative + @Override + public Vector getAdditionalTargets() { + return additionalTargets; + } + + //public Vector getEnumeratedDependencies() { + // return enumeratedDependencies; + //} + + @Override + public boolean isTargetTool() { + return bIsTargetTool; + } + + /* + * Other Methods + */ + + public boolean calculateInputs(GnuMakefileGenerator makeGen, IConfiguration config, IResource[] projResources, + ToolInfoHolder h, boolean lastChance) { + // Get the inputs for this tool invocation + // Note that command inputs that are also dependencies are also added to the command dependencies list + + /* The priorities for determining the names of the inputs of a tool are: + * 1. If an option is specified, use the value of the option. + * 2. If a build variable is specified, use the files that have been added to the build variable as + * the output(s) of other build steps. + * 3. Use the file extensions and the resources in the project + */ + boolean done = true; + Vector myCommandInputs = new Vector<>(); // Inputs for the tool command line + Vector myCommandDependencies = new Vector<>(); // Dependencies for the make rule + Vector myEnumeratedInputs = new Vector<>(); // Complete list of individual inputs + + IInputType[] inTypes = tool.getInputTypes(); + if (inTypes != null && inTypes.length > 0) { + for (IInputType type : inTypes) { + Vector itCommandInputs = new Vector<>(); // Inputs for the tool command line for this input-type + Vector itCommandDependencies = new Vector<>(); // Dependencies for the make rule for this input-type + Vector itEnumeratedInputs = new Vector<>(); // Complete list of individual inputs for this input-type + String variable = type.getBuildVariable(); + boolean primaryInput = type.getPrimaryInput(); + boolean useFileExts = false; + IOption option = tool.getOptionBySuperClassId(type.getOptionId()); + IOption assignToOption = tool.getOptionBySuperClassId(type.getAssignToOptionId()); + + // Option? + if (option != null) { + try { + List inputs = new ArrayList<>(); + int optType = option.getValueType(); + if (optType == IOption.STRING) { + inputs.add(option.getStringValue()); + } else if (optType == IOption.STRING_LIST || optType == IOption.LIBRARIES + || optType == IOption.OBJECTS || optType == IOption.INCLUDE_FILES + || optType == IOption.LIBRARY_PATHS || optType == IOption.LIBRARY_FILES + || optType == IOption.MACRO_FILES) { + @SuppressWarnings("unchecked") + List valueList = (List) option.getValue(); + inputs = valueList; + tool.filterValues(optType, inputs); + tool.filterValues(optType, inputs); + } + for (int j = 0; j < inputs.size(); j++) { + String inputName = inputs.get(j); + + try { + // try to resolve the build macros in the output + // names + + String resolved = null; + + // does the input name contain spaces? + // TODO: support other special characters + if (inputName.indexOf(" ") != -1) //$NON-NLS-1$ + { + // resolve to string + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValue(inputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_OPTION, new OptionContextData(option, tool)); + } else { + + // resolve to makefile variable format + resolved = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + inputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_OPTION, new OptionContextData(option, tool)); + } + + if ((resolved = resolved.trim()).length() > 0) + inputName = resolved; + } catch (BuildMacroException e) { + } + + if (primaryInput) { + itCommandDependencies.add(j, inputName); + } else { + itCommandDependencies.add(inputName); + } + // NO - itCommandInputs.add(inputName); + // NO - itEnumeratedInputs.add(inputName); + } + } catch (BuildException ex) { + } + + } else { + + // Build Variable? + if (variable.length() > 0) { + String cmdVariable = variable = "$(" + variable + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + itCommandInputs.add(cmdVariable); + if (primaryInput) { + itCommandDependencies.add(0, cmdVariable); + } else { + itCommandDependencies.add(cmdVariable); + } + // If there is an output variable with the same name, get + // the files associated with it. + List outMacroList = makeGen.getBuildVariableList(h, variable, + GnuMakefileGenerator.PROJECT_RELATIVE, null, true); + if (outMacroList != null) { + itEnumeratedInputs.addAll(outMacroList); + } else { + // If "last chance", then calculate using file extensions below + if (lastChance) { + useFileExts = true; + } else { + done = false; + break; + } + } + } + + // Use file extensions + if (variable.length() == 0 || useFileExts) { + //if (type.getMultipleOfType()) { + // Calculate EnumeratedInputs using the file extensions and the resources in the project + // Note: This is only correct for tools with multipleOfType == true, but for other tools + // it gives us an input resource for generating default names + // Determine the set of source input macros to use + HashSet handledInputExtensions = new HashSet<>(); + String[] exts = type.getSourceExtensions(tool); + if (projResources != null) { + for (IResource rc : projResources) { + if (rc.getType() == IResource.FILE) { + String fileExt = rc.getFileExtension(); + + // fix for NPE, bugzilla 99483 + if (fileExt == null) { + fileExt = ""; //$NON-NLS-1$ + } + + for (int k = 0; k < exts.length; k++) { + if (fileExt.equals(exts[k])) { + if (!useFileExts) { + if (!handledInputExtensions.contains(fileExt)) { + handledInputExtensions.add(fileExt); + String buildMacro = "$(" //$NON-NLS-1$ + + makeGen.getSourceMacroName(fileExt).toString() + ")"; //$NON-NLS-1$ + itCommandInputs.add(buildMacro); + if (primaryInput) { + itCommandDependencies.add(0, buildMacro); + } else { + itCommandDependencies.add(buildMacro); + } + } + } + if (type.getMultipleOfType() || itEnumeratedInputs.size() == 0) { + // Add a path that is relative to the project directory + itEnumeratedInputs.add(rc.getProjectRelativePath().toString()); + } + break; + } + } + } + } + } + //} + } + } + + // Get any additional inputs specified in the manifest file or the project file + IAdditionalInput[] addlInputs = type.getAdditionalInputs(); + if (addlInputs != null) { + for (int j = 0; j < addlInputs.length; j++) { + IAdditionalInput addlInput = addlInputs[j]; + int kind = addlInput.getKind(); + if (kind == IAdditionalInput.KIND_ADDITIONAL_INPUT + || kind == IAdditionalInput.KIND_ADDITIONAL_INPUT_DEPENDENCY) { + String[] paths = addlInput.getPaths(); + if (paths != null) { + for (int k = 0; k < paths.length; k++) { + String path = paths[k]; + itEnumeratedInputs.add(path); + // Translate the path from project relative to build directory relative + if (!(path.startsWith("$("))) { //$NON-NLS-1$ + IResource addlResource = project.getFile(path); + if (addlResource != null) { + IPath addlPath = addlResource.getLocation(); + if (addlPath != null) { + path = ManagedBuildManager + .calculateRelativePath(makeGen.getTopBuildDir(), addlPath) + .toString(); + } + } + } + itCommandInputs.add(path); + } + } + } + } + } + + // If the assignToOption attribute is specified, set the input(s) as the value of that option + if (assignToOption != null && option == null) { + try { + int optType = assignToOption.getValueType(); + if (optType == IOption.STRING) { + String optVal = ""; //$NON-NLS-1$ + for (int j = 0; j < itCommandInputs.size(); j++) { + if (j != 0) { + optVal += " "; //$NON-NLS-1$ + } + optVal += itCommandInputs.get(j); + } + ManagedBuildManager.setOption(config, tool, assignToOption, optVal); + } else if (optType == IOption.STRING_LIST || optType == IOption.LIBRARIES + || optType == IOption.OBJECTS || optType == IOption.INCLUDE_FILES + || optType == IOption.LIBRARY_PATHS || optType == IOption.LIBRARY_FILES + || optType == IOption.MACRO_FILES) { + //TODO: do we need to do anything with undefs here? + // Mote that when using the enumerated inputs, the path(s) must be translated from project relative + // to top build directory relative + String[] paths = new String[itEnumeratedInputs.size()]; + for (int j = 0; j < itEnumeratedInputs.size(); j++) { + paths[j] = itEnumeratedInputs.get(j); + IResource enumResource = project.getFile(paths[j]); + if (enumResource != null) { + IPath enumPath = enumResource.getLocation(); + if (enumPath != null) { + paths[j] = ManagedBuildManager + .calculateRelativePath(makeGen.getTopBuildDir(), enumPath).toString(); + } + } + } + ManagedBuildManager.setOption(config, tool, assignToOption, paths); + } else if (optType == IOption.BOOLEAN) { + if (itEnumeratedInputs.size() > 0) { + ManagedBuildManager.setOption(config, tool, assignToOption, true); + } else { + ManagedBuildManager.setOption(config, tool, assignToOption, false); + } + } else if (optType == IOption.ENUMERATED || optType == IOption.TREE) { + if (itCommandInputs.size() > 0) { + ManagedBuildManager.setOption(config, tool, assignToOption, + itCommandInputs.firstElement()); + } + } + itCommandInputs.removeAllElements(); + //itEnumeratedInputs.removeAllElements(); + } catch (BuildException ex) { + } + } + + myCommandInputs.addAll(itCommandInputs); + myCommandDependencies.addAll(itCommandDependencies); + myEnumeratedInputs.addAll(itEnumeratedInputs); + } + } else { + // For support of pre-CDT 3.0 integrations. + if (bIsTargetTool) { + // NOTE WELL: This only supports the case of a single "target tool" + // with the following characteristics: + // 1. The tool consumes exactly all of the object files produced + // by other tools in the build and produces a single output + // 2. The target name comes from the configuration artifact name + // The rule looks like: + // .: $(OBJS) + myCommandInputs.add("$(OBJS)"); //$NON-NLS-1$ + myCommandInputs.add("$(USER_OBJS)"); //$NON-NLS-1$ + myCommandInputs.add("$(LIBS)"); //$NON-NLS-1$ + } else { + // Rule will be generated by addRuleForSource + } + } + + if (done) { + commandInputs.addAll(myCommandInputs); + commandDependencies.addAll(0, myCommandDependencies); + enumeratedInputs.addAll(myEnumeratedInputs); + inputsCalculated = true; + return true; + } + + return false; + } + + /* + * The priorities for determining the names of the outputs of a tool are: + * 1. If the tool is the build target and primary output, use artifact name & extension + * 2. If an option is specified, use the value of the option + * 3. If a nameProvider is specified, call it + * 4. If outputNames is specified, use it + * 5. Use the name pattern to generate a transformation macro + * so that the source names can be transformed into the target names + * using the built-in string substitution functions of make. + * + * NOTE: If an option is not specified and this is not the primary output type, the outputs + * from the type are not added to the command line + */ + public boolean calculateOutputs(GnuMakefileGenerator makeGen, IConfiguration config, + HashSet handledInputExtensions, boolean lastChance) { + + boolean done = true; + Vector myCommandOutputs = new Vector<>(); + Vector myEnumeratedPrimaryOutputs = new Vector<>(); + Vector myEnumeratedSecondaryOutputs = new Vector<>(); + HashMap> myOutputMacros = new HashMap<>(); + // The next two fields are used together + Vector myBuildVars = new Vector<>(); + Vector> myBuildVarsValues = new Vector<>(); + + // Get the outputs for this tool invocation + IOutputType[] outTypes = tool.getOutputTypes(); + if (outTypes != null && outTypes.length > 0) { + for (int i = 0; i < outTypes.length; i++) { + Vector typeEnumeratedOutputs = new Vector<>(); + IOutputType type = outTypes[i]; + String outputPrefix = type.getOutputPrefix(); + + // Resolve any macros in the outputPrefix + // Note that we cannot use file macros because if we do a clean + // we need to know the actual name of the file to clean, and + // cannot use any builder variables such as $@. Hence we use the + // next best thing, i.e. configuration context. + + if (config != null) { + + try { + outputPrefix = ManagedBuildManager.getBuildMacroProvider().resolveValueToMakefileFormat( + outputPrefix, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + } + + catch (BuildMacroException e) { + } + } + + String variable = type.getBuildVariable(); + boolean multOfType = type.getMultipleOfType(); + boolean primaryOutput = (type == tool.getPrimaryOutputType()); + IOption option = tool.getOptionBySuperClassId(type.getOptionId()); + IManagedOutputNameProvider nameProvider = type.getNameProvider(); + String[] outputNames = type.getOutputNames(); + + // 1. If the tool is the build target and this is the primary output, + // use artifact name & extension + if (bIsTargetTool && primaryOutput) { + String outputName = outputPrefix + targetName; + if (targetExt.length() > 0) { + outputName += (DOT + targetExt); + } + myCommandOutputs.add(outputName); + typeEnumeratedOutputs.add(outputName); + // But this doesn't use any output macro... + } else + // 2. If an option is specified, use the value of the option + if (option != null) { + try { + List outputs = new ArrayList<>(); + int optType = option.getValueType(); + if (optType == IOption.STRING) { + outputs.add(outputPrefix + option.getStringValue()); + } else if (optType == IOption.STRING_LIST || optType == IOption.LIBRARIES + || optType == IOption.OBJECTS || optType == IOption.INCLUDE_FILES + || optType == IOption.LIBRARY_PATHS || optType == IOption.LIBRARY_FILES + || optType == IOption.MACRO_FILES) { + @SuppressWarnings("unchecked") + List value = (List) option.getValue(); + outputs = value; + tool.filterValues(optType, outputs); + // Add outputPrefix to each if necessary + if (outputPrefix.length() > 0) { + for (int j = 0; j < outputs.size(); j++) { + outputs.set(j, outputPrefix + outputs.get(j)); + } + } + } + for (int j = 0; j < outputs.size(); j++) { + String outputName = outputs.get(j); + try { + //try to resolve the build macros in the output names + String resolved = ManagedBuildManager.getBuildMacroProvider() + .resolveValueToMakefileFormat(outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_OPTION, + new OptionContextData(option, tool)); + if ((resolved = resolved.trim()).length() > 0) + outputs.set(j, resolved); + } catch (BuildMacroException e) { + } + } + + // NO - myCommandOutputs.addAll(outputs); + typeEnumeratedOutputs.addAll(outputs); + if (variable.length() > 0) { + List outputPaths = new ArrayList<>(); + for (int j = 0; j < outputs.size(); j++) { + outputPaths.add(Path.fromOSString(outputs.get(j))); + } + if (myOutputMacros.containsKey(variable)) { + List currList = myOutputMacros.get(variable); + currList.addAll(outputPaths); + myOutputMacros.put(variable, currList); + } else { + myOutputMacros.put(variable, outputPaths); + } + } + } catch (BuildException ex) { + } + } else + // 3. If a nameProvider is specified, call it + if (nameProvider != null) { + // The inputs must have been calculated before we can do this + IPath[] outNames = null; + if (!inputsCalculated) { + done = false; + } else { + Vector inputs = getEnumeratedInputs(); + IPath[] inputPaths = new IPath[inputs.size()]; + for (int j = 0; j < inputPaths.length; j++) { + inputPaths[j] = Path.fromOSString(inputs.get(j)); + } + outNames = nameProvider.getOutputNames(tool, inputPaths); + if (outNames != null) { + for (int j = 0; j < outNames.length; j++) { + String outputName = outNames[j].toString(); + try { + //try to resolve the build macros in the output names + String resolved = ManagedBuildManager.getBuildMacroProvider() + .resolveValueToMakefileFormat(outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_CONFIGURATION, config); + if ((resolved = resolved.trim()).length() > 0) { + outputName = resolved; + outNames[j] = Path.fromOSString(resolved); + } + } catch (BuildMacroException e) { + } + + if (primaryOutput) { + myCommandOutputs.add(outputName); + } + typeEnumeratedOutputs.add(outputName); + } + } + } + if (variable.length() > 0 && outNames != null) { + if (myOutputMacros.containsKey(variable)) { + List currList = myOutputMacros.get(variable); + currList.addAll(Arrays.asList(outNames)); + myOutputMacros.put(variable, currList); + } else { + myOutputMacros.put(variable, new ArrayList<>(Arrays.asList(outNames))); + } + } + } else + // 4. If outputNames is specified, use it + if (outputNames != null) { + if (outputNames.length > 0) { + for (int j = 0; j < outputNames.length; j++) { + String outputName = outputNames[j]; + try { + //try to resolve the build macros in the output names + String resolved = ManagedBuildManager.getBuildMacroProvider() + .resolveValueToMakefileFormat(outputName, "", //$NON-NLS-1$ + " ", //$NON-NLS-1$ + IBuildMacroProvider.CONTEXT_OPTION, + new OptionContextData(option, tool)); + if ((resolved = resolved.trim()).length() > 0) + outputNames[j] = resolved; + } catch (BuildMacroException e) { + } + } + List namesList = Arrays.asList(outputNames); + if (primaryOutput) { + myCommandOutputs.addAll(namesList); + } + typeEnumeratedOutputs.addAll(namesList); + if (variable.length() > 0) { + List outputPaths = new ArrayList<>(); + for (int j = 0; j < namesList.size(); j++) { + outputPaths.add(Path.fromOSString(namesList.get(j))); + } + if (myOutputMacros.containsKey(variable)) { + List currList = myOutputMacros.get(variable); + currList.addAll(outputPaths); + myOutputMacros.put(variable, currList); + } else { + myOutputMacros.put(variable, outputPaths); + } + } + } + } else { + // 5. Use the name pattern to generate a transformation macro + // so that the source names can be transformed into the target names + // using the built-in string substitution functions of make. + if (multOfType) { + // This case is not handled - a nameProvider or outputNames must be specified + List errList = new ArrayList<>(); + errList.add(ManagedMakeMessages.getResourceString("MakefileGenerator.error.no.nameprovider")); //$NON-NLS-1$ + myCommandOutputs.addAll(errList); + } else { + String namePattern = type.getNamePattern(); + if (namePattern == null || namePattern.length() == 0) { + namePattern = outputPrefix + IManagedBuilderMakefileGenerator.WILDCARD; + String outExt = (type.getOutputExtensions(tool))[0]; + if (outExt != null && outExt.length() > 0) { + namePattern += DOT + outExt; + } + } else if (outputPrefix.length() > 0) { + namePattern = outputPrefix + namePattern; + } + + // Calculate the output name + // The inputs must have been calculated before we can do this + if (!inputsCalculated) { + done = false; + } else { + Vector inputs = getEnumeratedInputs(); + String fileName; + if (inputs.size() > 0) { + // Get the input file name + fileName = (Path.fromOSString(inputs.get(0))).removeFileExtension().lastSegment(); + // Check if this is a build macro. If so, use the raw macro name. + if (fileName.startsWith("$(") && fileName.endsWith(")")) { //$NON-NLS-1$ //$NON-NLS-2$ + fileName = fileName.substring(2, fileName.length() - 1); + } + } else { + fileName = "default"; //$NON-NLS-1$ + } + // Replace the % with the file name + if (primaryOutput) { + myCommandOutputs.add(namePattern.replaceAll("%", fileName)); //$NON-NLS-1$ + } + typeEnumeratedOutputs.add(namePattern.replaceAll("%", fileName)); //$NON-NLS-1$ + if (variable.length() > 0) { + List outputs = new ArrayList<>(); + outputs.add(Path.fromOSString(fileName)); + if (myOutputMacros.containsKey(variable)) { + List currList = myOutputMacros.get(variable); + currList.addAll(outputs); + myOutputMacros.put(variable, currList); + } else { + myOutputMacros.put(variable, outputs); + } + } + } + } + } + if (variable.length() > 0) { + myBuildVars.add(variable); + myBuildVarsValues.add(typeEnumeratedOutputs); + } + if (primaryOutput) { + myEnumeratedPrimaryOutputs.addAll(typeEnumeratedOutputs); + } else { + myEnumeratedSecondaryOutputs.addAll(typeEnumeratedOutputs); + } + } + } else { + if (bIsTargetTool) { + String outputPrefix = tool.getOutputPrefix(); + String outputName = outputPrefix + targetName; + if (targetExt.length() > 0) { + outputName += (DOT + targetExt); + } + myCommandOutputs.add(outputName); + myEnumeratedPrimaryOutputs.add(outputName); + } else { + // For support of pre-CDT 3.0 integrations. + // NOTE WELL: This only supports the case of a single "target tool" + // that consumes exactly all of the object files, $OBJS, produced + // by other tools in the build and produces a single output + } + } + + // Add the output macros of this tool to the buildOutVars map + Set>> entrySet = myOutputMacros.entrySet(); + for (Entry> entry : entrySet) { + String macroName = entry.getKey(); + List newMacroValue = entry.getValue(); + HashMap> map = makeGen.getBuildOutputVars(); + if (map.containsKey(macroName)) { + List macroValue = map.get(macroName); + macroValue.addAll(newMacroValue); + map.put(macroName, macroValue); + } else { + map.put(macroName, newMacroValue); + } + } + outputVariablesCalculated = true; + + if (done) { + commandOutputs.addAll(myCommandOutputs); + enumeratedPrimaryOutputs.addAll(myEnumeratedPrimaryOutputs); + enumeratedSecondaryOutputs.addAll(myEnumeratedSecondaryOutputs); + outputVariables.addAll(myOutputMacros.keySet()); + outputsCalculated = true; + for (int i = 0; i < myBuildVars.size(); i++) { + makeGen.addMacroAdditionFiles(makeGen.getTopBuildOutputVars(), myBuildVars.get(i), + myBuildVarsValues.get(i)); + } + return true; + } + + return false; + } + + private boolean callDependencyCalculator(GnuMakefileGenerator makeGen, IConfiguration config, + HashSet handledInputExtensions, IManagedDependencyGeneratorType depGen, String[] extensionsList, + Vector myCommandDependencies, HashMap> myOutputMacros, + Vector myAdditionalTargets, ToolInfoHolder h, boolean done) { + + int calcType = depGen.getCalculatorType(); + switch (calcType) { + case IManagedDependencyGeneratorType.TYPE_COMMAND: + case IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS: + // iterate over all extensions that the tool knows how to handle + for (int i = 0; i < extensionsList.length; i++) { + String extensionName = extensionsList[i]; + + // Generated files should not appear in the list. + if (!makeGen.getOutputExtensions(h).contains(extensionName) + && !handledInputExtensions.contains(extensionName)) { + handledInputExtensions.add(extensionName); + String depExt = IManagedBuilderMakefileGenerator.DEP_EXT; + if (calcType == IManagedDependencyGeneratorType.TYPE_BUILD_COMMANDS) { + IManagedDependencyGenerator2 depGen2 = (IManagedDependencyGenerator2) depGen; + String xt = depGen2.getDependencyFileExtension(config, tool); + if (xt != null && xt.length() > 0) + depExt = xt; + } + String depsMacroEntry = calculateSourceMacro(makeGen, extensionName, depExt, + IManagedBuilderMakefileGenerator.WILDCARD); + + List depsList = new ArrayList<>(); + depsList.add(Path.fromOSString(depsMacroEntry)); + String depsMacro = makeGen.getDepMacroName(extensionName).toString(); + if (myOutputMacros.containsKey(depsMacro)) { + List currList = myOutputMacros.get(depsMacro); + currList.addAll(depsList); + myOutputMacros.put(depsMacro, currList); + } else { + myOutputMacros.put(depsMacro, depsList); + } + } + } + break; + + case IManagedDependencyGeneratorType.TYPE_INDEXER: + case IManagedDependencyGeneratorType.TYPE_EXTERNAL: + case IManagedDependencyGeneratorType.TYPE_CUSTOM: + // The inputs must have been calculated before we can do this + if (!inputsCalculated) { + done = false; + } else { + Vector inputs = getEnumeratedInputs(); + + if (calcType == IManagedDependencyGeneratorType.TYPE_CUSTOM) { + IManagedDependencyGenerator2 depGen2 = (IManagedDependencyGenerator2) depGen; + IManagedDependencyInfo depInfo = null; + for (int i = 0; i < inputs.size(); i++) { + + depInfo = depGen2.getDependencySourceInfo(Path.fromOSString(inputs.get(i)), config, tool, + makeGen.getBuildWorkingDir()); + + if (depInfo instanceof IManagedDependencyCalculator) { + IManagedDependencyCalculator depCalc = (IManagedDependencyCalculator) depInfo; + IPath[] depPaths = depCalc.getDependencies(); + if (depPaths != null) { + for (int j = 0; j < depPaths.length; j++) { + if (!depPaths[j].isAbsolute()) { + // Convert from project relative to build directory relative + IPath absolutePath = project.getLocation().append(depPaths[j]); + depPaths[j] = ManagedBuildManager + .calculateRelativePath(makeGen.getTopBuildDir(), absolutePath); + } + myCommandDependencies.add(depPaths[j].toString()); + } + } + IPath[] targetPaths = depCalc.getAdditionalTargets(); + if (targetPaths != null) { + for (int j = 0; j < targetPaths.length; j++) { + myAdditionalTargets.add(targetPaths[j].toString()); + } + } + } + } + } else { + IManagedDependencyGenerator oldDepGen = (IManagedDependencyGenerator) depGen; + for (String input : inputs) { + IResource[] outNames = oldDepGen.findDependencies(project.getFile(input), project); + if (outNames != null) { + for (IResource outName : outNames) { + myCommandDependencies.add(outName.toString()); + } + } + } + } + } + break; + + default: + break; + } + + return done; + } + + public boolean calculateDependencies(GnuMakefileGenerator makeGen, IConfiguration config, + HashSet handledInputExtensions, ToolInfoHolder h, boolean lastChance) { + // Get the dependencies for this tool invocation + boolean done = true; + Vector myCommandDependencies = new Vector<>(); + Vector myAdditionalTargets = new Vector<>(); + //Vector myEnumeratedDependencies = new Vector(); + HashMap> myOutputMacros = new HashMap<>(); + + IInputType[] inTypes = tool.getInputTypes(); + if (inTypes != null && inTypes.length > 0) { + for (int i = 0; i < inTypes.length; i++) { + IInputType type = inTypes[i]; + + // Handle dependencies from the dependencyCalculator + IManagedDependencyGeneratorType depGen = type.getDependencyGenerator(); + String[] extensionsList = type.getSourceExtensions(tool); + if (depGen != null) { + done = callDependencyCalculator(makeGen, config, handledInputExtensions, depGen, extensionsList, + myCommandDependencies, myOutputMacros, myAdditionalTargets, h, done); + } + + // Add additional dependencies specified in AdditionalInput elements + IAdditionalInput[] addlInputs = type.getAdditionalInputs(); + if (addlInputs != null && addlInputs.length > 0) { + for (int j = 0; j < addlInputs.length; j++) { + IAdditionalInput addlInput = addlInputs[j]; + int kind = addlInput.getKind(); + if (kind == IAdditionalInput.KIND_ADDITIONAL_DEPENDENCY + || kind == IAdditionalInput.KIND_ADDITIONAL_INPUT_DEPENDENCY) { + String[] paths = addlInput.getPaths(); + if (paths != null) { + for (int k = 0; k < paths.length; k++) { + // Translate the path from project relative to + // build directory relative + String path = paths[k]; + if (!(path.startsWith("$("))) { //$NON-NLS-1$ + IResource addlResource = project.getFile(path); + if (addlResource != null) { + IPath addlPath = addlResource.getLocation(); + if (addlPath != null) { + path = ManagedBuildManager + .calculateRelativePath(makeGen.getTopBuildDir(), addlPath) + .toString(); + } + } + } + myCommandDependencies.add(path); + //myEnumeratedInputs.add(path); + } + } + } + } + } + } + } else { + if (bIsTargetTool) { + // For support of pre-CDT 3.0 integrations. + // NOTE WELL: This only supports the case of a single "target tool" + // with the following characteristics: + // 1. The tool consumes exactly all of the object files produced + // by other tools in the build and produces a single output + // 2. The target name comes from the configuration artifact name + // The rule looks like: + // .: $(OBJS) + myCommandDependencies.add("$(OBJS)"); //$NON-NLS-1$ + myCommandDependencies.add("$(USER_OBJS)"); //$NON-NLS-1$ + } else { + // Handle dependencies from the dependencyCalculator + IManagedDependencyGeneratorType depGen = tool.getDependencyGenerator(); + String[] extensionsList = tool.getAllInputExtensions(); + if (depGen != null) { + done = callDependencyCalculator(makeGen, config, handledInputExtensions, depGen, extensionsList, + myCommandDependencies, myOutputMacros, myAdditionalTargets, h, done); + } + + } + } + + // Add the output macros of this tool to the buildOutVars map + Set>> entrySet = myOutputMacros.entrySet(); + for (Entry> entry : entrySet) { + String macroName = entry.getKey(); + List newMacroValue = entry.getValue(); + HashMap> map = makeGen.getBuildOutputVars(); + if (map.containsKey(macroName)) { + List macroValue = map.get(macroName); + macroValue.addAll(newMacroValue); + map.put(macroName, macroValue); + } else { + map.put(macroName, newMacroValue); + } + } + + if (done) { + commandDependencies.addAll(myCommandDependencies); + additionalTargets.addAll(myAdditionalTargets); + //enumeratedDependencies.addAll(myEnumeratedDependencies); + dependenciesCalculated = true; + return true; + } + + return false; + } + + /* + * Calculate the source macro for the given extension + */ + protected String calculateSourceMacro(GnuMakefileGenerator makeGen, String srcExtensionName, + String outExtensionName, String wildcard) { + StringBuffer macroName = makeGen.getSourceMacroName(srcExtensionName); + String OptDotExt = ""; //$NON-NLS-1$ + if (outExtensionName != null) { + OptDotExt = DOT + outExtensionName; + } else if (!tool.getOutputExtension(srcExtensionName).isEmpty()) + OptDotExt = DOT + tool.getOutputExtension(srcExtensionName); + + // create rule of the form + // OBJS = $(macroName1: ../%.input1=%.output1) ... $(macroNameN: ../%.inputN=%.outputN) + return IManagedBuilderMakefileGenerator.WHITESPACE + "$(" + macroName + //$NON-NLS-1$ + IManagedBuilderMakefileGenerator.COLON + makeGen.reachProjectRoot() + + IManagedBuilderMakefileGenerator.SEPARATOR + IManagedBuilderMakefileGenerator.WILDCARD + DOT + + srcExtensionName + "=" + wildcard + OptDotExt + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/build/org.eclipse.cdt.managedbuilder.gnu.ui/META-INF/MANIFEST.MF b/build/org.eclipse.cdt.managedbuilder.gnu.ui/META-INF/MANIFEST.MF index 68d3b38d8d5..3f24da3ce7b 100644 --- a/build/org.eclipse.cdt.managedbuilder.gnu.ui/META-INF/MANIFEST.MF +++ b/build/org.eclipse.cdt.managedbuilder.gnu.ui/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.cdt.managedbuilder.gnu.ui; singleton:=true -Bundle-Version: 8.4.100.qualifier +Bundle-Version: 8.4.200.qualifier Bundle-Activator: org.eclipse.cdt.managedbuilder.gnu.ui.GnuUIPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/build/org.eclipse.cdt.managedbuilder.gnu.ui/plugin.xml b/build/org.eclipse.cdt.managedbuilder.gnu.ui/plugin.xml index 3b4273dad01..06a8414c4e9 100644 --- a/build/org.eclipse.cdt.managedbuilder.gnu.ui/plugin.xml +++ b/build/org.eclipse.cdt.managedbuilder.gnu.ui/plugin.xml @@ -2168,7 +2168,7 @@ variableFormat="${=}" isVariableCaseSensitive="true" reservedMacroNames="ROOT,DEPS,OBJS,.*_SRCS,EXECUTABLES,SUBDIRS,LIBS,USER_OBJS,.*_INPUTS,.*_OUTPUTS" - buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator" + buildfileGenerator="org.eclipse.cdt.managedbuilder.makegen.gnu2.GnuMakefileGenerator" macroInputFileNameValue="$(notdir $<)" macroInputFileExtValue="$(suffix $(notdir $<))" macroInputFileBaseNameValue="$(basename $(notdir $<))" diff --git a/doc/org.eclipse.cdt.doc.isv/guide/deprecated_API_removals.html b/doc/org.eclipse.cdt.doc.isv/guide/deprecated_API_removals.html index b6fd67dbd20..5b9d5e6d62b 100644 --- a/doc/org.eclipse.cdt.doc.isv/guide/deprecated_API_removals.html +++ b/doc/org.eclipse.cdt.doc.isv/guide/deprecated_API_removals.html @@ -63,6 +63,12 @@

  • Removed unneded boolean from function
  • Changed methods from static to non-static
  • +

    + Planned Removals after December 2023 +

    +
      +
    1. GnuMakefileGenerator is no longer part of API
    2. +

    API Changes prior to CDT 10.0 / 2020-09. @@ -488,6 +494,22 @@

    See Bug 573722.

    + + +

    API Removals after December 2023

    + +

    1. GnuMakefileGenerator is no longer part of API

    +

    + The following classes have been removed from the API. +

    +
      +
    • org.eclipse.cdt.managedbuilder.makegen.gnu.GnuMakefileGenerator
    • +
    • org.eclipse.cdt.managedbuilder.makegen.gnu.IManagedBuildGnuToolInfo
    • +
    • org.eclipse.cdt.managedbuilder.makegen.gnu.ManagedBuildGnuToolInfo
    • +
    +

    + See Bug 505882. +