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. +