/******************************************************************************* * Copyright (c) 2005, 2016 Intel Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Intel Corporation - Initial API and implementation * IBM Corporation * Marc-Andre Laperle *******************************************************************************/ package org.eclipse.cdt.managedbuilder.internal.core; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.eclipse.cdt.core.cdtvariables.CdtVariableException; import org.eclipse.cdt.core.settings.model.ICStorageElement; import org.eclipse.cdt.core.settings.model.util.CDataUtil; import org.eclipse.cdt.internal.core.SafeStringInterner; import org.eclipse.cdt.managedbuilder.core.BuildException; import org.eclipse.cdt.managedbuilder.core.IAdditionalInput; import org.eclipse.cdt.managedbuilder.core.IBuildObject; import org.eclipse.cdt.managedbuilder.core.IBuilder; import org.eclipse.cdt.managedbuilder.core.IConfiguration; import org.eclipse.cdt.managedbuilder.core.IFileInfo; import org.eclipse.cdt.managedbuilder.core.IInputType; import org.eclipse.cdt.managedbuilder.core.IManagedConfigElement; 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.IToolChain; import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin; import org.eclipse.cdt.managedbuilder.internal.macros.BuildMacroProvider; import org.eclipse.cdt.managedbuilder.internal.macros.IMacroContextInfo; 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.utils.EFSExtensionManager; import org.eclipse.cdt.utils.cdtvariables.CdtVariableResolver; import org.eclipse.cdt.utils.cdtvariables.SupplierBasedCdtVariableSubstitutor; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; public class AdditionalInput implements IAdditionalInput { private static final String BUILD_VARIABLE_STATIC_LIB = "ARCHIVES"; //$NON-NLS-1$ private static final String BUILD_VARIABLE_SHARED_LIB = "LIBRARIES"; //$NON-NLS-1$ private String[] expandedNames; // Superclass // Parent and children private IInputType fParent; // Managed Build model attributes private String fPaths; private Integer fKind; // Miscellaneous private boolean fIsExtensionAdditionalInput = false; private boolean fIsDirty = false; private boolean fResolved = true; private boolean fRebuildState; /* * C O N S T R U C T O R S */ /** * This constructor is called to create an AdditionalInput defined by an extension point in * a plugin manifest file, or returned by a dynamic element provider * * @param parent The IInputType parent of this AdditionalInput * @param element The AdditionalInput definition from the manifest file or a dynamic element * provider */ public AdditionalInput(IInputType parent, IManagedConfigElement element) { this.fParent = parent; fIsExtensionAdditionalInput = true; // setup for resolving fResolved = false; loadFromManifest(element); } /** * This constructor is called to create an AdditionalInput whose attributes and children will be * added by separate calls. * * @param parent The parent of the an AdditionalInput * @param isExtensionElement Indicates whether this is an extension element or a managed project element */ public AdditionalInput(InputType parent, boolean isExtensionElement) { this.fParent = parent; fIsExtensionAdditionalInput = isExtensionElement; if (!isExtensionElement) { setDirty(true); setRebuildState(true); } } /** * Create an <code>AdditionalInput</code> based on the specification stored in the * project file (.cdtbuild). * * @param parent The <code>ITool</code> the AdditionalInput will be added to. * @param element The XML element that contains the AdditionalInput settings. */ public AdditionalInput(IInputType parent, ICStorageElement element) { this.fParent = parent; fIsExtensionAdditionalInput = false; // Initialize from the XML attributes loadFromProject(element); } /** * Create an <code>AdditionalInput</code> based upon an existing AdditionalInput. * * @param parent The <code>IInputType</code> the AdditionalInput will be added to. * @param additionalInput The existing AdditionalInput to clone. */ public AdditionalInput(IInputType parent, AdditionalInput additionalInput) { this.fParent = parent; fIsExtensionAdditionalInput = false; // Copy the remaining attributes if (additionalInput.fPaths != null) { fPaths = additionalInput.fPaths; } if (additionalInput.fKind != null) { fKind = additionalInput.fKind; } setDirty(true); setRebuildState(true); } /* * E L E M E N T A T T R I B U T E R E A D E R S A N D W R I T E R S */ /* (non-Javadoc) * Loads the AdditionalInput information from the ManagedConfigElement specified in the * argument. * * @param element Contains the AdditionalInput information */ protected void loadFromManifest(IManagedConfigElement element) { // path fPaths = SafeStringInterner.safeIntern(element.getAttribute(IAdditionalInput.PATHS)); // kind String kindStr = element.getAttribute(IAdditionalInput.KIND); if (kindStr == null || kindStr.equals(ADDITIONAL_INPUT_DEPENDENCY)) { fKind = Integer.valueOf(KIND_ADDITIONAL_INPUT_DEPENDENCY); } else if (kindStr.equals(ADDITIONAL_INPUT)) { fKind = Integer.valueOf(KIND_ADDITIONAL_INPUT); } else if (kindStr.equals(ADDITIONAL_DEPENDENCY)) { fKind = Integer.valueOf(KIND_ADDITIONAL_DEPENDENCY); } } /* (non-Javadoc) * Initialize the AdditionalInput information from the XML element * specified in the argument * * @param element An XML element containing the AdditionalInput information */ protected void loadFromProject(ICStorageElement element) { // path if (element.getAttribute(IAdditionalInput.PATHS) != null) { fPaths = SafeStringInterner.safeIntern(element.getAttribute(IAdditionalInput.PATHS)); } // kind if (element.getAttribute(IAdditionalInput.KIND) != null) { String kindStr = element.getAttribute(IAdditionalInput.KIND); if (kindStr == null || kindStr.equals(ADDITIONAL_INPUT_DEPENDENCY)) { fKind = Integer.valueOf(KIND_ADDITIONAL_INPUT_DEPENDENCY); } else if (kindStr.equals(ADDITIONAL_INPUT)) { fKind = Integer.valueOf(KIND_ADDITIONAL_INPUT); } else if (kindStr.equals(ADDITIONAL_DEPENDENCY)) { fKind = Integer.valueOf(KIND_ADDITIONAL_DEPENDENCY); } } } /** * Persist the AdditionalInput to the project file. */ public void serialize(ICStorageElement element) { if (fPaths != null) { element.setAttribute(IAdditionalInput.PATHS, fPaths); } if (fKind != null) { String str; switch (getKind()) { case KIND_ADDITIONAL_INPUT: str = ADDITIONAL_INPUT; break; case KIND_ADDITIONAL_DEPENDENCY: str = ADDITIONAL_DEPENDENCY; break; case KIND_ADDITIONAL_INPUT_DEPENDENCY: str = ADDITIONAL_INPUT_DEPENDENCY; break; default: str = ""; //$NON-NLS-1$ break; } element.setAttribute(IAdditionalInput.KIND, str); } // I am clean now fIsDirty = false; } /* * P A R E N T A N D C H I L D H A N D L I N G */ /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IAdditionalInput#getParent() */ @Override public IInputType getParent() { return fParent; } /* * M O D E L A T T R I B U T E A C C E S S O R S */ /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IAdditionalInput#getPaths() */ @Override public String[] getPaths() { if (fPaths == null) { return null; } String[] nameTokens = CDataUtil.stringToArray(fPaths, ";"); //$NON-NLS-1$ return nameTokens; } /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IAdditionalInput#setPaths() */ @Override public void setPaths(String newPaths) { if (fPaths == null && newPaths == null) return; if (fPaths == null || newPaths == null || !(fPaths.equals(newPaths))) { fPaths = newPaths; fIsDirty = true; setRebuildState(true); } } /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IAdditionalInput#getKind() */ @Override public int getKind() { if (fKind == null) { return KIND_ADDITIONAL_INPUT_DEPENDENCY; } return fKind.intValue(); } /* (non-Javadoc) * @see org.eclipse.cdt.core.build.managed.IAdditionalInput#setKind() */ @Override public void setKind(int newKind) { if (fKind == null || !(fKind.intValue() == newKind)) { fKind = Integer.valueOf(newKind); fIsDirty = true; setRebuildState(true); } } /* * O B J E C T S T A T E M A I N T E N A N C E */ /* (non-Javadoc) * @see org.eclipse.cdt.managedbuilder.core.IAdditionalInput#isExtensionElement() */ public boolean isExtensionElement() { return fIsExtensionAdditionalInput; } /* (non-Javadoc) * @see org.eclipse.cdt.managedbuilder.core.IAdditionalInput#isDirty() */ @Override public boolean isDirty() { // This shouldn't be called for an extension AdditionalInput if (fIsExtensionAdditionalInput) return false; return fIsDirty; } /* (non-Javadoc) * @see org.eclipse.cdt.managedbuilder.core.IAdditionalInput#setDirty(boolean) */ @Override public void setDirty(boolean isDirty) { this.fIsDirty = isDirty; } /* (non-Javadoc) * Resolve the element IDs to interface references */ public void resolveReferences() { if (!fResolved) { fResolved = true; } } public boolean needsRebuild() { // This shouldn't be called for an extension AdditionalInput if (fIsExtensionAdditionalInput) return false; if (fRebuildState) return fRebuildState; if (fKind.intValue() == IAdditionalInput.KIND_ADDITIONAL_DEPENDENCY || fKind.intValue() == IAdditionalInput.KIND_ADDITIONAL_INPUT_DEPENDENCY || isLibrariesInput()) { IToolChain toolChain = getToolChain(); /* toolChain can be null e.g. in tools for custom build steps */ if (toolChain != null && !toolChain.isExtensionElement()) { long artifactTimeStamp = getArtifactTimeStamp(toolChain); if (0 != artifactTimeStamp) { String[] paths = getPaths(); for (int i = 0; i < paths.length; ++i) { if (paths[i].length() == 0) continue; if (dependencyChanged(paths[i], artifactTimeStamp)) return true; } } } } return false; } private long getArtifactTimeStamp(IToolChain toolChain) { IBuilder builder = toolChain.getBuilder(); IConfiguration configuration = toolChain.getParent(); URI buildLocationURI = ManagedBuildManager.getBuildLocationURI(configuration, builder); if (buildLocationURI != null) { if (!buildLocationURI.toString().endsWith("/")) { //$NON-NLS-1$ // ensure that it's a directory URI buildLocationURI = URI.create(buildLocationURI.toString() + "/"); //$NON-NLS-1$ } String artifactName = configuration.getArtifactName(); String artifactExt = configuration.getArtifactExtension(); String artifactPref = configuration.getOutputPrefix(artifactExt); if (artifactName.length() > 0) { if (artifactExt.length() > 0) artifactName += "." + artifactExt; //$NON-NLS-1$ if (artifactPref.length() > 0) artifactName = artifactPref + artifactName; try { artifactName = ManagedBuildManager.getBuildMacroProvider().resolveValue(artifactName, "", //$NON-NLS-1$ " ", IBuildMacroProvider.CONTEXT_CONFIGURATION, configuration); //$NON-NLS-1$ } catch (BuildMacroException e) { } URI buildArtifactURI = EFSExtensionManager.getDefault().append(buildLocationURI, artifactName); try { IFileStore artifact = EFS.getStore(buildArtifactURI); org.eclipse.core.filesystem.IFileInfo info = (artifact == null) ? null : artifact.fetchInfo(); if ((info != null) && info.exists()) { return info.getLastModified(); } } catch (CoreException e) { // if we can't even inquire about it, then assume it doesn't exist } } } return 0; } private boolean isLibrariesInput() { // libraries are of the "additionalinput" kind, not "additionalinputdependency" because otherwise the // external make builder would generate makefiles with $(LIBS) in the dependency list, resulting in // failure to build dependency -lxyz etc. return (fKind.intValue() == IAdditionalInput.KIND_ADDITIONAL_INPUT && Arrays.asList(getPaths()).contains("$(LIBS)")); //$NON-NLS-1$ } private boolean dependencyChanged(String sPath, long artefactTimeStamp) { try { IToolChain toolChain = getToolChain(); IConfiguration configuration = toolChain.getParent(); if (fIsDirty || (null == expandedNames)) { if ("$(LIBS)".equals(sPath)) //$NON-NLS-1$ expandedNames = getDepLibs(); else if ("$(USER_OBJS)".equals(sPath)) //$NON-NLS-1$ expandedNames = getDepObjs(configuration); else { expandedNames = getDepFiles(sPath); } } for (int j = 0; j < expandedNames.length; ++j) { if (expandedNames[j] != null) { IFileStore file = getEFSFile(expandedNames[j]); org.eclipse.core.filesystem.IFileInfo info = (file == null) ? null : file.fetchInfo(); if ((info != null) && info.exists() && (info.getLastModified() > artefactTimeStamp)) { return true; } } } } catch (Exception e) { // we'll have to assume that the dependency didn't change if we couldn't get its timestamp ManagedBuilderCorePlugin.log(e); } return false; } private IToolChain getToolChain() { IBuildObject bo = fParent.getParent().getParent(); IToolChain tCh = null; if (bo instanceof IToolChain) { tCh = ((IToolChain) bo); } else if (bo instanceof IFileInfo) { tCh = ((ResourceConfiguration) bo).getBaseToolChain(); } return tCh; } private String[] getDepLibs() throws CoreException, BuildException, CdtVariableException { IOption[] options = fParent.getParent().getOptions(); String[] libNames = null; List<String> libPaths = null; for (int i = 0; i < options.length; ++i) { int type = options[i].getValueType(); if (type == IOption.LIBRARIES) { libNames = options[i].getLibraries(); } else if (type == IOption.LIBRARY_PATHS) { if (null == libPaths) libPaths = new ArrayList<String>(); libPaths.addAll(Arrays.asList(restoreLibraryPaths(options[i]))); } } if ((libNames != null) && (libPaths != null)) { IToolChain toolChain = getToolChain(); for (int i = 0; i < libNames.length; ++i) { URI uri = findLibrary(toolChain, libNames[i], libPaths); libNames[i] = (uri == null) ? null : uri.toString(); } return libNames; } return new String[0]; } private String[] restoreLibraryPaths(IOption option) throws BuildException, CdtVariableException { @SuppressWarnings("unchecked") List<String> libPaths = (List<String>) option.getValue(); String[] dirs = libPaths.toArray(new String[libPaths.size()]); dirs = substituteEnvVars(option, dirs); return dirs; } private URI findLibrary(IToolChain toolChain, final String libName, List<String> dirs) throws CoreException { final String libSO = getDynamicLibPrefix(toolChain) + libName + '.' + getDynamicLibExtension(toolChain); final String libA = getStaticLibPrefix(toolChain) + libName + '.' + getStaticLibExtension(toolChain); class LibFilter { public boolean accept(String name) { if (equals(libA, name)) return true; if (!startsWith(name, libSO)) return false; if (libSO.length() == name.length()) return true; // we don't necessarily have a version extension if (name.charAt(libSO.length()) != '.') return false; String ext = libName.substring(libSO.length() + 1); try { Integer.parseInt(ext); return true; } catch (NumberFormatException e) { return false; } } boolean equals(String a, String b) { return a.equals(b); } boolean startsWith(String string, String prefix) { return string.startsWith(prefix); } } class CaseInsensitiveLibFilter extends LibFilter { @Override boolean equals(String a, String b) { return a.equalsIgnoreCase(b); } @Override boolean startsWith(String string, String prefix) { return string.toLowerCase().startsWith(prefix.toLowerCase()); } } for (Iterator<String> i = dirs.iterator(); i.hasNext();) { IFileStore dir = getEFSFile(i.next()); LibFilter filter = dir.getFileSystem().isCaseSensitive() ? new LibFilter() : new CaseInsensitiveLibFilter(); for (IFileStore child : dir.childStores(EFS.NONE, null)) { if (filter.accept(child.getName())) { return child.toURI(); } } } return null; } /** * Gets an EFS file-store for the specified path or URI, which may be a local filesystem path or may be a more abstract URI. * * @param pathOrURI a local filesystem path or URI * * @return the file store, if one could be determined */ private static IFileStore getEFSFile(String pathOrURI) { IFileStore result; try { // try it as a URI result = EFS.getStore(URI.create(pathOrURI)); } catch (Exception e) { // most likely, the path is not a URI, so assume a local file and try again result = EFS.getLocalFileSystem().getStore(new Path(pathOrURI)); } return result; } private String[] substituteEnvVars(IOption option, String[] paths) throws CdtVariableException { BuildMacroProvider provider = (BuildMacroProvider) ManagedBuildManager.getBuildMacroProvider(); IMacroContextInfo info = provider.getMacroContextInfo(IBuildMacroProvider.CONTEXT_OPTION, new OptionContextData(option, fParent)); String delimiter = ManagedBuildManager.getEnvironmentVariableProvider().getDefaultDelimiter(); String inexVal = " "; //$NON-NLS-1$ SupplierBasedCdtVariableSubstitutor subst = provider.getMacroSubstitutor(info, inexVal, delimiter); String[] newPaths = CdtVariableResolver.resolveStringListValues(paths, subst, false); for (int i = 0; i < newPaths.length; ++i) { String newPath = newPaths[i]; int len = newPath.length(); if ((len > 1) && (newPath.charAt(0) == '\"') && (newPath.charAt(len - 1) == '\"')) newPaths[i] = newPaths[i].substring(1, len - 1); } return newPaths; } private static String getStaticLibPrefix(IToolChain toolChain) { IOutputType type = findOutputType(toolChain, BUILD_VARIABLE_STATIC_LIB); if (null == type) return "lib"; //$NON-NLS-1$ return type.getOutputPrefix(); } private static String getStaticLibExtension(IToolChain toolChain) { IOutputType type = findOutputType(toolChain, BUILD_VARIABLE_STATIC_LIB); if (null == type || type.getOutputExtensionsAttribute().length == 0) { return "a"; //$NON-NLS-1$ } return type.getOutputExtensionsAttribute()[0]; } private static String getDynamicLibPrefix(IToolChain toolChain) { IOutputType type = findOutputType(toolChain, BUILD_VARIABLE_SHARED_LIB); if (null == type) return "lib"; //$NON-NLS-1$ return type.getOutputPrefix(); } private static String getDynamicLibExtension(IToolChain toolChain) { IOutputType type = findOutputType(toolChain, BUILD_VARIABLE_SHARED_LIB); if (null == type || type.getOutputExtensionsAttribute().length == 0) { return "so"; //$NON-NLS-1$ } return type.getOutputExtensionsAttribute()[0]; } public static IOutputType findOutputType(IToolChain toolChain, String buildVariable) { // if we're determining whether to re-build an executable, then it won't have an output // type for shared libraries from which we can get a filename extension or prefix. // We have to scan the extension toolchain toolChain = ManagedBuildManager.getExtensionToolChain(toolChain); ITool[] tools = toolChain.getTools(); for (int i = 0; i < tools.length; ++i) { IOutputType[] oTypes = tools[i].getOutputTypes(); for (int j = 0; j < oTypes.length; ++j) { if (buildVariable.equals(oTypes[j].getBuildVariable())) return oTypes[j]; } } return null; } private String[] getDepObjs(IConfiguration configuration) throws BuildException, CdtVariableException { IOption[] options = fParent.getParent().getOptions(); String[] userObjs = null; for (int i = 0; i < options.length; ++i) { int type = options[i].getValueType(); if (type == IOption.OBJECTS) { userObjs = options[i].getUserObjects(); return substituteEnvVars(options[i], userObjs); } } return new String[0]; } private String[] getDepFiles(String sPath) { return new String[0]; } public void setRebuildState(boolean rebuild){ if(isExtensionElement() && rebuild) return; fRebuildState = rebuild; } }