| /******************************************************************************* |
| * Copyright (c) 2015, 2022 QNX Software Systems 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 |
| *******************************************************************************/ |
| package org.eclipse.cdt.core.build; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.net.URI; |
| import java.nio.file.Files; |
| import java.nio.file.InvalidPathException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.eclipse.cdt.core.CCorePlugin; |
| import org.eclipse.cdt.core.CommandLauncherManager; |
| import org.eclipse.cdt.core.IConsoleParser; |
| import org.eclipse.cdt.core.IConsoleParser2; |
| import org.eclipse.cdt.core.IMarkerGenerator; |
| import org.eclipse.cdt.core.ProblemMarkerInfo; |
| import org.eclipse.cdt.core.envvar.IEnvironmentVariable; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.model.ElementChangedEvent; |
| import org.eclipse.cdt.core.model.IBinary; |
| import org.eclipse.cdt.core.model.IBinaryContainer; |
| import org.eclipse.cdt.core.model.ICElement; |
| import org.eclipse.cdt.core.model.ICElementDelta; |
| import org.eclipse.cdt.core.model.ICModelMarker; |
| import org.eclipse.cdt.core.model.ICProject; |
| import org.eclipse.cdt.core.model.IElementChangedListener; |
| import org.eclipse.cdt.core.model.IIncludeEntry; |
| import org.eclipse.cdt.core.model.IIncludeFileEntry; |
| import org.eclipse.cdt.core.model.IMacroEntry; |
| import org.eclipse.cdt.core.model.IMacroFileEntry; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.core.parser.ExtendedScannerInfo; |
| import org.eclipse.cdt.core.parser.IExtendedScannerInfo; |
| import org.eclipse.cdt.core.parser.IScannerInfo; |
| import org.eclipse.cdt.core.parser.IScannerInfoChangeListener; |
| import org.eclipse.cdt.core.resources.IConsole; |
| import org.eclipse.cdt.internal.core.build.Messages; |
| import org.eclipse.cdt.internal.core.model.BinaryRunner; |
| import org.eclipse.cdt.internal.core.model.CModelManager; |
| import org.eclipse.cdt.internal.core.scannerinfo.ExtendedScannerInfoSerializer; |
| import org.eclipse.cdt.internal.core.scannerinfo.IExtendedScannerInfoDeserializer; |
| import org.eclipse.cdt.utils.CommandLineUtil; |
| import org.eclipse.cdt.utils.spawner.EnvironmentReader; |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.resources.IBuildConfiguration; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IProjectDescription; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.PlatformObject; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.osgi.service.prefs.Preferences; |
| |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| |
| /** |
| * Root class for CDT build configurations. Provides access to the build |
| * settings for subclasses. |
| * |
| * @since 6.0 |
| */ |
| public abstract class CBuildConfiguration extends PlatformObject implements ICBuildConfiguration, ICBuildConfiguration2, |
| IMarkerGenerator, IConsoleParser2, IElementChangedListener { |
| |
| private static final String LAUNCH_MODE = "cdt.launchMode"; //$NON-NLS-1$ |
| |
| private static final String NEED_REFRESH = "cdt.needScannerRefresh"; //$NON-NLS-1$ |
| |
| private static final List<String> DEFAULT_COMMAND = new ArrayList<>(0); |
| |
| private final String name; |
| private final IBuildConfiguration config; |
| private final IToolChain toolChain; |
| private String launchMode; |
| |
| private Object scannerInfoLock = new Object(); |
| |
| private final Map<IResource, List<IScannerInfoChangeListener>> scannerInfoListeners = new HashMap<>(); |
| private ScannerInfoCache scannerInfoCache; |
| |
| protected CBuildConfiguration(IBuildConfiguration config, String name) throws CoreException { |
| this.config = config; |
| this.name = name; |
| |
| Preferences settings = getSettings(); |
| String typeId = settings.get(TOOLCHAIN_TYPE, ""); //$NON-NLS-1$ |
| String id = settings.get(TOOLCHAIN_ID, ""); //$NON-NLS-1$ |
| IToolChainManager toolChainManager = CCorePlugin.getService(IToolChainManager.class); |
| IToolChain tc = toolChainManager.getToolChain(typeId, id); |
| |
| if (tc == null) { |
| // check for other versions |
| tc = toolChainManager.getToolChain(typeId, id); |
| if (tc == null) { |
| throw new CoreException( |
| new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, CCorePlugin.STATUS_BUILD_CONFIG_NOT_VALID, |
| String.format(Messages.CBuildConfiguration_ToolchainMissing, config.getName()), null)); |
| } |
| } |
| this.toolChain = tc; |
| |
| this.launchMode = settings.get(LAUNCH_MODE, "run"); //$NON-NLS-1$ |
| |
| CoreModel.getDefault().addElementChangedListener(this); |
| } |
| |
| protected CBuildConfiguration(IBuildConfiguration config, String name, IToolChain toolChain) { |
| this(config, name, toolChain, "run"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| protected CBuildConfiguration(IBuildConfiguration config, String name, IToolChain toolChain, String launchMode) { |
| this.config = config; |
| this.name = name; |
| this.toolChain = toolChain; |
| this.launchMode = launchMode; |
| |
| Preferences settings = getSettings(); |
| settings.put(TOOLCHAIN_TYPE, toolChain.getTypeId()); |
| settings.put(TOOLCHAIN_ID, toolChain.getId()); |
| if (launchMode != null) { |
| settings.put(LAUNCH_MODE, launchMode); |
| } |
| try { |
| settings.flush(); |
| } catch (BackingStoreException e) { |
| CCorePlugin.log(e); |
| } |
| |
| CoreModel.getDefault().addElementChangedListener(this); |
| } |
| |
| protected CBuildConfiguration(IBuildConfiguration config, IToolChain toolChain) { |
| this(config, DEFAULT_NAME, toolChain); |
| } |
| |
| @Override |
| public IBuildConfiguration getBuildConfiguration() { |
| return config; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| @Override |
| public String getLaunchMode() { |
| return launchMode; |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| protected void setLaunchMode(String launchMode) { |
| this.launchMode = launchMode; |
| Preferences settings = getSettings(); |
| settings.put(LAUNCH_MODE, launchMode); |
| try { |
| settings.flush(); |
| } catch (BackingStoreException e) { |
| CCorePlugin.log(e); |
| } |
| } |
| |
| public IProject getProject() { |
| return config.getProject(); |
| } |
| |
| @Override |
| public String getBinaryParserId() throws CoreException { |
| return toolChain != null ? toolChain.getBinaryParserId() : CCorePlugin.DEFAULT_BINARY_PARSER_UNIQ_ID; |
| } |
| |
| public IContainer getBuildContainer() throws CoreException { |
| // TODO make the name of this folder a project property |
| IProject project = getProject(); |
| IFolder buildRootFolder = project.getFolder("build"); //$NON-NLS-1$ |
| IFolder buildFolder = buildRootFolder.getFolder(name); |
| |
| IProgressMonitor monitor = new NullProgressMonitor(); |
| if (!buildRootFolder.exists()) { |
| buildRootFolder.create(IResource.FORCE | IResource.DERIVED, true, monitor); |
| } |
| if (!buildFolder.exists()) { |
| buildFolder.create(IResource.FORCE | IResource.DERIVED, true, monitor); |
| } |
| |
| return buildFolder; |
| } |
| |
| @Override |
| public URI getBuildDirectoryURI() throws CoreException { |
| return getBuildContainer().getLocationURI(); |
| } |
| |
| public Path getBuildDirectory() throws CoreException { |
| return Paths.get(getBuildDirectoryURI()); |
| } |
| |
| @Override |
| public void setBuildEnvironment(Map<String, String> env) { |
| CCorePlugin.getDefault().getBuildEnvironmentManager().setEnvironment(env, config, true); |
| } |
| |
| /** |
| * @since 6.1 |
| */ |
| @Override |
| public IBinary[] getBuildOutput() throws CoreException { |
| ICProject cproject = CoreModel.getDefault().create(config.getProject()); |
| IBinaryContainer binaries = cproject.getBinaryContainer(); |
| IPath outputPath = getBuildContainer().getFullPath(); |
| final IBinary[] outputs = getBuildOutput(binaries, outputPath); |
| if (outputs.length > 0) { |
| return outputs; |
| } |
| |
| // Give the binary runner a kick and try again. |
| BinaryRunner runner = CModelManager.getDefault().getBinaryRunner(cproject); |
| runner.start(); |
| runner.waitIfRunning(); |
| return getBuildOutput(binaries, outputPath); |
| } |
| |
| private IBinary[] getBuildOutput(final IBinaryContainer binaries, final IPath outputPath) throws CoreException { |
| return Arrays.stream(binaries.getBinaries()).filter(b -> b.isExecutable() && outputPath.isPrefixOf(b.getPath())) |
| .toArray(IBinary[]::new); |
| } |
| |
| public void setActive(IProgressMonitor monitor) throws CoreException { |
| IProject project = config.getProject(); |
| if (config.equals(project.getActiveBuildConfig())) { |
| // already set |
| return; |
| } |
| |
| CoreModel m = CoreModel.getDefault(); |
| synchronized (m) { |
| IProjectDescription projectDesc = project.getDescription(); |
| IBuildConfiguration[] bconfigs = project.getBuildConfigs(); |
| Set<String> names = new LinkedHashSet<>(); |
| for (IBuildConfiguration bconfig : bconfigs) { |
| names.add(bconfig.getName()); |
| } |
| // must add default config name as it may not be in build config list |
| names.add(IBuildConfiguration.DEFAULT_CONFIG_NAME); |
| // ensure active config is last in list so clean build will clean |
| // active config last and this will be left in build console for user to see |
| names.remove(config.getName()); |
| names.add(config.getName()); |
| |
| projectDesc.setBuildConfigs(names.toArray(new String[0])); |
| projectDesc.setActiveBuildConfig(config.getName()); |
| project.setDescription(projectDesc, monitor); |
| } |
| } |
| |
| protected Preferences getSettings() { |
| return InstanceScope.INSTANCE.getNode(CCorePlugin.PLUGIN_ID).node("config") //$NON-NLS-1$ |
| .node(getProject().getName()).node(config.getName()); |
| } |
| |
| @Override |
| public IToolChain getToolChain() throws CoreException { |
| return toolChain; |
| } |
| |
| @Override |
| public IEnvironmentVariable getVariable(String name) { |
| IEnvironmentVariable[] vars = getVariables(); |
| if (vars != null) { |
| for (IEnvironmentVariable var : vars) { |
| if (var.getName().equals(name)) { |
| return var; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public IEnvironmentVariable[] getVariables() { |
| // by default, none |
| return null; |
| } |
| |
| @Override |
| public void addMarker(IResource file, int lineNumber, String errorDesc, int severity, String errorVar) { |
| addMarker(new ProblemMarkerInfo(file, lineNumber, errorDesc, severity, errorVar, null)); |
| } |
| |
| @Override |
| public void addMarker(ProblemMarkerInfo problemMarkerInfo) { |
| try { |
| IProject project = config.getProject(); |
| IResource markerResource = problemMarkerInfo.file; |
| if (markerResource == null) { |
| markerResource = project; |
| } |
| String externalLocation = null; |
| if (problemMarkerInfo.externalPath != null && !problemMarkerInfo.externalPath.isEmpty()) { |
| externalLocation = problemMarkerInfo.externalPath.toOSString(); |
| } |
| |
| // Try to find matching markers and don't put in duplicates |
| IMarker[] markers = markerResource.findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, |
| IResource.DEPTH_ONE); |
| for (IMarker m : markers) { |
| int line = m.getAttribute(IMarker.LINE_NUMBER, -1); |
| int sev = m.getAttribute(IMarker.SEVERITY, -1); |
| String msg = (String) m.getAttribute(IMarker.MESSAGE); |
| if (line == problemMarkerInfo.lineNumber && sev == mapMarkerSeverity(problemMarkerInfo.severity) |
| && msg.equals(problemMarkerInfo.description)) { |
| String extloc = (String) m.getAttribute(ICModelMarker.C_MODEL_MARKER_EXTERNAL_LOCATION); |
| if (extloc == externalLocation || (extloc != null && extloc.equals(externalLocation))) { |
| if (project == null || project.equals(markerResource.getProject())) { |
| return; |
| } |
| String source = (String) m.getAttribute(IMarker.SOURCE_ID); |
| if (project.getName().equals(source)) { |
| return; |
| } |
| } |
| } |
| } |
| |
| String type = problemMarkerInfo.getType(); |
| if (type == null) { |
| type = ICModelMarker.C_MODEL_PROBLEM_MARKER; |
| } |
| |
| IMarker marker = markerResource.createMarker(type); |
| marker.setAttribute(IMarker.MESSAGE, problemMarkerInfo.description); |
| marker.setAttribute(IMarker.SEVERITY, mapMarkerSeverity(problemMarkerInfo.severity)); |
| marker.setAttribute(IMarker.LINE_NUMBER, problemMarkerInfo.lineNumber); |
| marker.setAttribute(IMarker.CHAR_START, problemMarkerInfo.startChar); |
| marker.setAttribute(IMarker.CHAR_END, problemMarkerInfo.endChar); |
| if (problemMarkerInfo.variableName != null) { |
| marker.setAttribute(ICModelMarker.C_MODEL_MARKER_VARIABLE, problemMarkerInfo.variableName); |
| } |
| if (externalLocation != null) { |
| URI uri = URIUtil.toURI(externalLocation); |
| if (uri.getScheme() != null) { |
| marker.setAttribute(ICModelMarker.C_MODEL_MARKER_EXTERNAL_LOCATION, externalLocation); |
| String locationText = String.format(Messages.CBuildConfiguration_Location, |
| problemMarkerInfo.lineNumber, externalLocation); |
| marker.setAttribute(IMarker.LOCATION, locationText); |
| } |
| } else if (problemMarkerInfo.lineNumber == 0) { |
| marker.setAttribute(IMarker.LOCATION, " "); //$NON-NLS-1$ |
| } |
| // Set source attribute only if the marker is being set to a file |
| // from different project |
| if (project != null && !project.equals(markerResource.getProject())) { |
| marker.setAttribute(IMarker.SOURCE_ID, project.getName()); |
| } |
| |
| // Add all other client defined attributes. |
| Map<String, String> attributes = problemMarkerInfo.getAttributes(); |
| if (attributes != null) { |
| for (Entry<String, String> entry : attributes.entrySet()) { |
| marker.setAttribute(entry.getKey(), entry.getValue()); |
| } |
| } |
| } catch (CoreException e) { |
| CCorePlugin.log(e.getStatus()); |
| } |
| } |
| |
| private int mapMarkerSeverity(int severity) { |
| switch (severity) { |
| case SEVERITY_ERROR_BUILD: |
| case SEVERITY_ERROR_RESOURCE: |
| return IMarker.SEVERITY_ERROR; |
| case SEVERITY_INFO: |
| return IMarker.SEVERITY_INFO; |
| case SEVERITY_WARNING: |
| return IMarker.SEVERITY_WARNING; |
| } |
| return IMarker.SEVERITY_ERROR; |
| } |
| |
| protected Path findCommand(String command) { |
| try { |
| Path cmdPath = Paths.get(command); |
| if (cmdPath.isAbsolute()) { |
| return cmdPath; |
| } |
| Properties environmentVariables = EnvironmentReader.getEnvVars(); |
| Map<String, String> env = new HashMap<>(); |
| for (String key : environmentVariables.stringPropertyNames()) { |
| String value = environmentVariables.getProperty(key); |
| env.put(key, value); |
| } |
| |
| setBuildEnvironment(env); |
| |
| String pathStr = env.get("PATH"); //$NON-NLS-1$ |
| if (pathStr == null) { |
| return null; // no idea |
| } |
| String[] path = pathStr.split(File.pathSeparator); |
| for (String dir : path) { |
| Path commandPath = Paths.get(dir, command); |
| if (Files.exists(commandPath) && Files.isRegularFile(commandPath)) { |
| return commandPath; |
| } else { |
| if (Platform.getOS().equals(Platform.OS_WIN32) |
| && !(command.endsWith(".exe") || command.endsWith(".bat"))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| commandPath = Paths.get(dir, command + ".exe"); //$NON-NLS-1$ |
| if (Files.exists(commandPath)) { |
| return commandPath; |
| } |
| } |
| } |
| } |
| IToolChain tc = getToolChain(); |
| if (tc instanceof IToolChain2) { |
| // we may have a Container build...default to Path based on command |
| return Paths.get(command); |
| } |
| } catch (InvalidPathException e) { |
| // ignore |
| } catch (CoreException e) { |
| // ignore |
| } |
| return null; |
| } |
| |
| /** |
| * @since 6.5 |
| */ |
| public Process startBuildProcess(List<String> commands, IEnvironmentVariable[] envVars, IPath buildDirectory, |
| IConsole console, IProgressMonitor monitor) throws IOException, CoreException { |
| Process process = null; |
| IToolChain tc = getToolChain(); |
| if (tc instanceof IToolChain2) { |
| process = ((IToolChain2) tc).startBuildProcess(this, commands, buildDirectory.toString(), envVars, console, |
| monitor); |
| } else { |
| // verify command can be found locally on path |
| Path commandPath = findCommand(commands.get(0)); |
| if (commandPath == null) { |
| console.getErrorStream() |
| .write(String.format(Messages.CBuildConfiguration_CommandNotFound, commands.get(0))); |
| return null; |
| } |
| commands.set(0, commandPath.toString()); |
| |
| // check if includes have been removed/refreshed and scanner info refresh is needed |
| boolean needRefresh = CommandLauncherManager.getInstance().checkIfIncludesChanged(this); |
| IToolChain t = getToolChain(); |
| if (t != null) { |
| t.setProperty(NEED_REFRESH, Boolean.valueOf(needRefresh).toString()); |
| } |
| |
| ProcessBuilder processBuilder = new ProcessBuilder(commands).directory(buildDirectory.toFile()); |
| // Override environment variables |
| Map<String, String> environment = processBuilder.environment(); |
| for (IEnvironmentVariable envVar : envVars) { |
| environment.put(envVar.getName(), envVar.getValue()); |
| } |
| setBuildEnvironment(environment); |
| process = processBuilder.start(); |
| } |
| return process; |
| } |
| |
| @Deprecated |
| protected int watchProcess(Process process, IConsoleParser[] consoleParsers, IConsole console) |
| throws CoreException { |
| if (consoleParsers == null || consoleParsers.length == 0) { |
| return watchProcess(process, console); |
| } else { |
| return watchProcess(process, consoleParsers); |
| } |
| } |
| |
| /** |
| * @since 6.4 |
| */ |
| protected int watchProcess(Process process, IConsole console) throws CoreException { |
| Thread t1 = new ReaderThread(process.getInputStream(), console.getOutputStream()); |
| t1.start(); |
| Thread t2 = new ReaderThread(process.getErrorStream(), console.getErrorStream()); |
| t2.start(); |
| try { |
| int rc = process.waitFor(); |
| // Allow reader threads the chance to process all output to console |
| while (t1.isAlive()) { |
| Thread.sleep(100); |
| } |
| while (t2.isAlive()) { |
| Thread.sleep(100); |
| } |
| return rc; |
| } catch (InterruptedException e) { |
| CCorePlugin.log(e); |
| return -1; |
| } |
| } |
| |
| /** |
| * @since 6.4 |
| */ |
| protected int watchProcess(Process process, IConsoleParser[] consoleParsers) throws CoreException { |
| Thread t1 = new ReaderThread(this, process.getInputStream(), consoleParsers); |
| t1.start(); |
| Thread t2 = new ReaderThread(this, process.getErrorStream(), consoleParsers); |
| t2.start(); |
| try { |
| int rc = process.waitFor(); |
| // Allow reader threads the chance to process all output to console |
| while (t1.isAlive()) { |
| Thread.sleep(100); |
| } |
| while (t2.isAlive()) { |
| Thread.sleep(100); |
| } |
| return rc; |
| } catch (InterruptedException e) { |
| CCorePlugin.log(e); |
| return -1; |
| } |
| } |
| |
| private static class ReaderThread extends Thread { |
| CBuildConfiguration config; |
| private final BufferedReader in; |
| private final IConsoleParser[] consoleParsers; |
| private final PrintStream out; |
| |
| public ReaderThread(CBuildConfiguration config, InputStream in, IConsoleParser[] consoleParsers) { |
| this.config = config; |
| this.in = new BufferedReader(new InputStreamReader(in)); |
| this.out = null; |
| this.consoleParsers = consoleParsers; |
| } |
| |
| public ReaderThread(InputStream in, OutputStream out) { |
| this.in = new BufferedReader(new InputStreamReader(in)); |
| this.out = new PrintStream(out); |
| this.consoleParsers = null; |
| this.config = null; |
| } |
| |
| @Override |
| public void run() { |
| List<Job> jobList = new ArrayList<>(); |
| try { |
| for (String line = in.readLine(); line != null; line = in.readLine()) { |
| if (consoleParsers != null) { |
| for (IConsoleParser consoleParser : consoleParsers) { |
| // Synchronize to avoid interleaving of lines |
| synchronized (consoleParser) { |
| // if we have an IConsoleParser2, use the processLine method that |
| // takes a job list (Container Build support) |
| if (consoleParser instanceof IConsoleParser2) { |
| ((IConsoleParser2) consoleParser).processLine(line, jobList); |
| } else { |
| consoleParser.processLine(line); |
| } |
| } |
| } |
| } |
| if (out != null) { |
| out.println(line); |
| } |
| } |
| for (Job j : jobList) { |
| try { |
| j.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| if (config != null) { |
| config.shutdown(); |
| } |
| } catch (IOException e) { |
| CCorePlugin.log(e); |
| } |
| } |
| } |
| |
| private File getScannerInfoCacheFile() { |
| return CCorePlugin.getDefault().getStateLocation().append("infoCache") //$NON-NLS-1$ |
| .append(getProject().getName()).append(name + ".json").toFile(); //$NON-NLS-1$ |
| } |
| |
| /** |
| * @since 6.1 |
| */ |
| protected void loadScannerInfoCache() { |
| synchronized (scannerInfoLock) { |
| if (scannerInfoCache == null) { |
| File cacheFile = getScannerInfoCacheFile(); |
| if (cacheFile.exists()) { |
| try (FileReader reader = new FileReader(cacheFile)) { |
| Gson gson = createGson(); |
| scannerInfoCache = gson.fromJson(reader, ScannerInfoCache.class); |
| } catch (IOException e) { |
| CCorePlugin.log(e); |
| } |
| } |
| |
| if (scannerInfoCache == null) { |
| scannerInfoCache = new ScannerInfoCache(); |
| } |
| scannerInfoCache.initCache(); |
| } |
| } |
| } |
| |
| private Gson createGson() { |
| GsonBuilder gsonBuilder = new GsonBuilder(); |
| gsonBuilder.registerTypeAdapter(IExtendedScannerInfo.class, new IExtendedScannerInfoDeserializer()); |
| gsonBuilder.registerTypeAdapter(ExtendedScannerInfo.class, new ExtendedScannerInfoSerializer()); |
| Gson gson = gsonBuilder.create(); |
| return gson; |
| } |
| |
| /** |
| * @since 6.1 |
| */ |
| protected synchronized void saveScannerInfoCache() { |
| File cacheFile = getScannerInfoCacheFile(); |
| if (!cacheFile.getParentFile().exists()) { |
| try { |
| Files.createDirectories(cacheFile.getParentFile().toPath()); |
| } catch (IOException e) { |
| CCorePlugin.log(e); |
| return; |
| } |
| } |
| |
| try (FileWriter writer = new FileWriter(getScannerInfoCacheFile())) { |
| Gson gson = createGson(); |
| synchronized (scannerInfoLock) { |
| gson.toJson(scannerInfoCache, writer); |
| } |
| } catch (IOException e) { |
| CCorePlugin.log(e); |
| } |
| } |
| |
| /** |
| * @since 6.1 |
| */ |
| protected ScannerInfoCache getScannerInfoCache() { |
| return scannerInfoCache; |
| } |
| |
| private IExtendedScannerInfo getBaseScannerInfo(IResource resource) throws CoreException { |
| IPath resPath = resource.getFullPath(); |
| IIncludeEntry[] includeEntries = CoreModel.getIncludeEntries(resPath); |
| String[] includes = new String[includeEntries.length]; |
| for (int i = 0; i < includeEntries.length; ++i) { |
| includes[i] = includeEntries[i].getFullIncludePath().toOSString(); |
| } |
| |
| IIncludeFileEntry[] includeFileEntries = CoreModel.getIncludeFileEntries(resPath); |
| String[] includeFiles = new String[includeFileEntries.length]; |
| for (int i = 0; i < includeFiles.length; ++i) { |
| includeFiles[i] = includeFileEntries[i].getFullIncludeFilePath().toOSString(); |
| } |
| |
| IMacroEntry[] macros = CoreModel.getMacroEntries(resPath); |
| Map<String, String> symbolMap = new HashMap<>(); |
| for (int i = 0; i < macros.length; ++i) { |
| symbolMap.put(macros[i].getMacroName(), macros[i].getMacroValue()); |
| } |
| |
| IMacroFileEntry[] macroFileEntries = CoreModel.getMacroFileEntries(resPath); |
| String[] macroFiles = new String[macroFileEntries.length]; |
| for (int i = 0; i < macroFiles.length; ++i) { |
| macroFiles[i] = macroFileEntries[i].getFullMacroFilePath().toOSString(); |
| } |
| return new ExtendedScannerInfo(symbolMap, includes, includeFiles, macroFiles); |
| } |
| |
| @Override |
| public IScannerInfo getScannerInformation(IResource resource) { |
| loadScannerInfoCache(); |
| IExtendedScannerInfo info = null; |
| synchronized (scannerInfoLock) { |
| info = scannerInfoCache.getScannerInfo(resource); |
| } |
| // Following is a kludge to fix Bug 579668 whereby sometimes a timing |
| // bug occurs and scanner info for a project that specifies a container target |
| // has not initialized the include paths correctly to point to copied includes |
| // from the image target. We check to see if org.eclipse.cdt.docker.launcher is |
| // found in the include paths which is the .plugin directory where we copy headers |
| // to in the .metadata folder. |
| boolean needsFixing = false; |
| if (info != null && info.getIncludePaths().length > 0 && toolChain != null |
| && toolChain.getId().startsWith("gcc-img-sha")) { //$NON-NLS-1$ |
| needsFixing = true; |
| for (String includePath : info.getIncludePaths()) { |
| if (includePath.contains("org.eclipse.cdt.docker.launcher")) { //$NON-NLS-1$ |
| needsFixing = false; |
| break; |
| } |
| } |
| } |
| if (info == null || info.getIncludePaths().length == 0 || needsFixing) { |
| ICElement celement = CCorePlugin.getDefault().getCoreModel().create(resource); |
| if (celement instanceof ITranslationUnit) { |
| try { |
| ITranslationUnit tu = (ITranslationUnit) celement; |
| info = getToolChain().getDefaultScannerInfo(getBuildConfiguration(), getBaseScannerInfo(resource), |
| tu.getLanguage(), getBuildDirectoryURI()); |
| synchronized (scannerInfoLock) { |
| scannerInfoCache.addScannerInfo(DEFAULT_COMMAND, info, resource); |
| } |
| saveScannerInfoCache(); |
| } catch (CoreException e) { |
| CCorePlugin.log(e.getStatus()); |
| } |
| } |
| } |
| return info; |
| } |
| |
| @Override |
| public void elementChanged(ElementChangedEvent event) { |
| // check if the path entries changed in the project and clear the cache if so |
| processElementDelta(event.getDelta()); |
| } |
| |
| private void processElementDelta(ICElementDelta delta) { |
| if (delta == null) { |
| return; |
| } |
| |
| int flags = delta.getFlags(); |
| int kind = delta.getKind(); |
| if (kind == ICElementDelta.CHANGED) { |
| if ((flags |
| & (ICElementDelta.F_CHANGED_PATHENTRY_INCLUDE | ICElementDelta.F_CHANGED_PATHENTRY_MACRO)) != 0) { |
| IResource resource = delta.getElement().getResource(); |
| if (resource.getProject().equals(getProject())) { |
| loadScannerInfoCache(); |
| synchronized (scannerInfoLock) { |
| if (scannerInfoCache.hasResource(DEFAULT_COMMAND, resource)) { |
| scannerInfoCache.removeResource(resource); |
| } else { |
| // Clear the whole command and exit the delta |
| scannerInfoCache.removeCommand(DEFAULT_COMMAND); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| ICElementDelta[] affectedChildren = delta.getAffectedChildren(); |
| for (int i = 0; i < affectedChildren.length; i++) { |
| processElementDelta(affectedChildren[i]); |
| } |
| } |
| |
| /** |
| * Parse a string containing compile options into individual argument strings. |
| * |
| * @param argString - String to parse |
| * @return List of arg Strings |
| */ |
| private List<String> stripArgs(String argString) { |
| String[] args = CommandLineUtil.argumentsToArray(argString); |
| return new ArrayList<>(Arrays.asList(args)); |
| } |
| |
| private boolean infoChanged = false; |
| |
| @Override |
| public boolean processLine(String line) { |
| // Split line into args, taking into account quotes |
| List<String> command = stripArgs(line); |
| |
| // Make sure it's a compile command |
| String[] compileCommands = toolChain.getCompileCommands(); |
| boolean found = false; |
| loop: for (String arg : command) { |
| // TODO we should really ask the toolchain, not all args start with '-' |
| if (arg.startsWith("-")) { //$NON-NLS-1$ |
| // option found, missed our command |
| return false; |
| } |
| |
| for (String cc : compileCommands) { |
| if (arg.endsWith(cc) && (arg.equals(cc) || arg.endsWith("/" + cc) || arg.endsWith("\\" + cc))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| found = true; |
| break loop; |
| } |
| } |
| |
| if (Platform.getOS().equals(Platform.OS_WIN32) && !arg.endsWith(".exe")) { //$NON-NLS-1$ |
| // Try with exe |
| arg = arg + ".exe"; //$NON-NLS-1$ |
| for (String cc : compileCommands) { |
| if (arg.endsWith(cc) && (arg.equals(cc) || arg.endsWith("/" + cc) || arg.endsWith("\\" + cc))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| found = true; |
| break loop; |
| } |
| } |
| } |
| } |
| |
| if (!found) { |
| return false; |
| } |
| |
| try { |
| IResource[] resources = toolChain.getResourcesFromCommand(command, getBuildDirectoryURI()); |
| if (resources != null && resources.length > 0) { |
| List<String> commandStrings = toolChain.stripCommand(command, resources); |
| |
| boolean needScannerRefresh = false; |
| |
| String needRefresh = toolChain.getProperty(NEED_REFRESH); |
| if ("true".equals(needRefresh)) { //$NON-NLS-1$ |
| needScannerRefresh = true; |
| } |
| |
| for (IResource resource : resources) { |
| loadScannerInfoCache(); |
| boolean hasCommand = true; |
| synchronized (scannerInfoLock) { |
| if (scannerInfoCache.hasCommand(commandStrings)) { |
| IExtendedScannerInfo info = scannerInfoCache.getScannerInfo(commandStrings); |
| if (info.getIncludePaths().length == 0) { |
| needScannerRefresh = true; |
| } |
| if (!scannerInfoCache.hasResource(commandStrings, resource)) { |
| scannerInfoCache.addResource(commandStrings, resource); |
| infoChanged = true; |
| } |
| } else { |
| hasCommand = false; |
| } |
| } |
| if (!hasCommand || needScannerRefresh) { |
| Path commandPath = findCommand(command.get(0)); |
| if (commandPath != null) { |
| command.set(0, commandPath.toString()); |
| IExtendedScannerInfo info = getToolChain().getScannerInfo(getBuildConfiguration(), command, |
| null, resource, getBuildDirectoryURI()); |
| synchronized (scannerInfoLock) { |
| scannerInfoCache.addScannerInfo(commandStrings, info, resource); |
| infoChanged = true; |
| } |
| } |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } catch (CoreException e) { |
| CCorePlugin.log(e); |
| return false; |
| } |
| } |
| |
| private class ScannerInfoJob extends Job { |
| private IToolChain toolchain; |
| private List<String> command; |
| private List<String> commandStrings; |
| private IResource resource; |
| private URI buildDirectoryURI; |
| |
| public ScannerInfoJob(String msg, IToolChain toolchain, List<String> command, IResource resource, |
| URI buildDirectoryURI, List<String> commandStrings) { |
| super(msg); |
| this.toolchain = toolchain; |
| this.command = command; |
| this.commandStrings = commandStrings; |
| this.resource = resource; |
| this.buildDirectoryURI = buildDirectoryURI; |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| IExtendedScannerInfo info = toolchain.getScannerInfo(getBuildConfiguration(), command, null, resource, |
| buildDirectoryURI); |
| synchronized (scannerInfoLock) { |
| scannerInfoCache.addScannerInfo(commandStrings, info, resource); |
| infoChanged = true; |
| } |
| return Status.OK_STATUS; |
| } |
| } |
| |
| /** |
| * Process a compile line for Scanner info in a separate job |
| * |
| * @param line - line to process |
| * @param jobsArray - array of Jobs to keep track of open scanner info jobs |
| * @return - true if line processed, false otherwise |
| * |
| * @since 6.5 |
| */ |
| @Override |
| public boolean processLine(String line, List<Job> jobsArray) { |
| // Split line into args, taking into account quotes |
| List<String> command = stripArgs(line); |
| |
| // Make sure it's a compile command |
| String[] compileCommands = toolChain.getCompileCommands(); |
| boolean found = false; |
| loop: for (String arg : command) { |
| // TODO we should really ask the toolchain, not all args start with '-' |
| if (arg.startsWith("-")) { //$NON-NLS-1$ |
| // option found, missed our command |
| return false; |
| } |
| |
| for (String cc : compileCommands) { |
| if (arg.endsWith(cc) && (arg.equals(cc) || arg.endsWith("/" + cc) || arg.endsWith("\\" + cc))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| found = true; |
| break loop; |
| } |
| } |
| |
| if (Platform.getOS().equals(Platform.OS_WIN32) && !arg.endsWith(".exe")) { //$NON-NLS-1$ |
| // Try with exe |
| arg = arg + ".exe"; //$NON-NLS-1$ |
| for (String cc : compileCommands) { |
| if (arg.endsWith(cc) && (arg.equals(cc) || arg.endsWith("/" + cc) || arg.endsWith("\\" + cc))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| found = true; |
| break loop; |
| } |
| } |
| } |
| } |
| |
| if (!found) { |
| return false; |
| } |
| |
| try { |
| IResource[] resources = toolChain.getResourcesFromCommand(command, getBuildDirectoryURI()); |
| if (resources != null && resources.length > 0) { |
| List<String> commandStrings = toolChain.stripCommand(command, resources); |
| |
| boolean needScannerRefresh = false; |
| |
| String needRefresh = toolChain.getProperty(NEED_REFRESH); |
| if ("true".equals(needRefresh)) { //$NON-NLS-1$ |
| needScannerRefresh = true; |
| } |
| |
| for (IResource resource : resources) { |
| loadScannerInfoCache(); |
| boolean hasCommand = true; |
| synchronized (scannerInfoLock) { |
| if (scannerInfoCache.hasCommand(commandStrings)) { |
| IExtendedScannerInfo info = scannerInfoCache.getScannerInfo(commandStrings); |
| if (info.getIncludePaths().length == 0) { |
| needScannerRefresh = true; |
| } |
| if (!scannerInfoCache.hasResource(commandStrings, resource)) { |
| scannerInfoCache.addResource(commandStrings, resource); |
| infoChanged = true; |
| } |
| } else { |
| hasCommand = false; |
| } |
| } |
| if (!hasCommand || needScannerRefresh) { |
| Path commandPath = findCommand(command.get(0)); |
| if (commandPath != null) { |
| command.set(0, commandPath.toString()); |
| Job job = new ScannerInfoJob( |
| String.format(Messages.CBuildConfiguration_RunningScannerInfo, resource), |
| getToolChain(), command, resource, getBuildDirectoryURI(), commandStrings); |
| job.schedule(); |
| jobsArray.add(job); |
| } |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } catch (CoreException e) { |
| CCorePlugin.log(e); |
| return false; |
| } |
| } |
| |
| /** |
| * @since 6.5 |
| */ |
| @Override |
| public void setActive() { |
| try { |
| refreshScannerInfo(); |
| } catch (CoreException e) { |
| // do nothing |
| } |
| } |
| |
| /** |
| * @since 6.5 |
| * @throws CoreException |
| */ |
| protected void refreshScannerInfo() throws CoreException { |
| CCorePlugin.getIndexManager().reindex(CoreModel.getDefault().create(getProject())); |
| infoChanged = false; |
| } |
| |
| @Override |
| public void shutdown() { |
| // TODO persist changes |
| |
| // Trigger a reindex if anything changed |
| // TODO be more surgical |
| if (infoChanged) { |
| saveScannerInfoCache(); |
| CCorePlugin.getIndexManager().reindex(CoreModel.getDefault().create(getProject())); |
| infoChanged = false; |
| } |
| } |
| |
| @Override |
| public void subscribe(IResource resource, IScannerInfoChangeListener listener) { |
| List<IScannerInfoChangeListener> listeners = scannerInfoListeners.get(resource); |
| if (listeners == null) { |
| listeners = new ArrayList<>(); |
| scannerInfoListeners.put(resource, listeners); |
| } |
| listeners.add(listener); |
| } |
| |
| @Override |
| public void unsubscribe(IResource resource, IScannerInfoChangeListener listener) { |
| List<IScannerInfoChangeListener> listeners = scannerInfoListeners.get(resource); |
| if (listeners != null) { |
| listeners.remove(listener); |
| if (listeners.isEmpty()) { |
| scannerInfoListeners.remove(resource); |
| } |
| } |
| } |
| |
| /** |
| * Takes a command path and returns either the command path itself if it is |
| * absolute or the path to the command as it appears in the PATH environment |
| * variable. Also adjusts the command for Windows's .exe extension. |
| * |
| * @since 6.1 |
| */ |
| public static Path getCommandFromPath(Path command) { |
| if (command.isAbsolute()) { |
| return command; |
| } |
| |
| if (Platform.getOS().equals(Platform.OS_WIN32)) { |
| if (!command.toString().endsWith(".exe")) { //$NON-NLS-1$ |
| command = Paths.get(command.toString() + ".exe"); //$NON-NLS-1$ |
| } |
| } |
| |
| // Look for it in the path environment var |
| String path = System.getenv("PATH"); //$NON-NLS-1$ |
| for (String entry : path.split(File.pathSeparator)) { |
| Path entryPath = Paths.get(entry); |
| Path cmdPath = entryPath.resolve(command); |
| if (Files.isExecutable(cmdPath)) { |
| return cmdPath; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| @Override |
| public boolean setProperties(Map<String, String> properties) { |
| Preferences settings = getSettings(); |
| for (Entry<String, String> entry : properties.entrySet()) { |
| settings.put(entry.getKey(), entry.getValue()); |
| } |
| return true; |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| @Override |
| public Map<String, String> getProperties() { |
| Map<String, String> properties = new HashMap<>(); |
| Preferences settings = getSettings(); |
| try { |
| for (String key : settings.keys()) { |
| String value = settings.get(key, null); |
| if (value != null) { |
| properties.put(key, value); |
| } |
| } |
| } catch (BackingStoreException e) { |
| CCorePlugin.log(e); |
| } |
| return properties; |
| } |
| |
| /** |
| * @since 6.4 |
| */ |
| @Override |
| public String getProperty(String name) { |
| return getSettings().get(name, null); |
| } |
| |
| /** |
| * @since 6.4 |
| */ |
| @Override |
| public void setProperty(String name, String value) { |
| Preferences settings = getSettings(); |
| settings.put(name, value); |
| } |
| |
| @Override |
| public void removeProperty(String name) { |
| Preferences settings = getSettings(); |
| settings.remove(name); |
| } |
| |
| /** |
| * @since 6.2 |
| */ |
| @Override |
| public Map<String, String> getDefaultProperties() { |
| return new HashMap<>(); |
| } |
| |
| } |