| /******************************************************************************* |
| * Copyright (c) 2014, 2018 Red Hat, Inc. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Red Hat Inc. |
| *******************************************************************************/ |
| |
| package org.eclipse.linuxtools.profiling.ui; |
| |
| import org.eclipse.cdt.autotools.core.AutotoolsNewProjectNature; |
| import org.eclipse.cdt.core.CCProjectNature; |
| import org.eclipse.cdt.core.CProjectNature; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; |
| import org.eclipse.cdt.core.settings.model.extension.CConfigurationData; |
| import org.eclipse.cdt.internal.autotools.core.configure.AutotoolsConfigurationManager; |
| import org.eclipse.cdt.internal.autotools.core.configure.IAConfiguration; |
| import org.eclipse.cdt.internal.autotools.core.configure.IConfigureOption; |
| import org.eclipse.cdt.internal.core.settings.model.CConfigurationDescriptionCache; |
| import org.eclipse.cdt.managedbuilder.core.BuildException; |
| import org.eclipse.cdt.managedbuilder.core.IBuilder; |
| import org.eclipse.cdt.managedbuilder.core.IConfiguration; |
| import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo; |
| import org.eclipse.cdt.managedbuilder.core.IOption; |
| import org.eclipse.cdt.managedbuilder.core.IResourceInfo; |
| import org.eclipse.cdt.managedbuilder.core.ITool; |
| import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; |
| import org.eclipse.cdt.managedbuilder.internal.dataprovider.BuildConfigurationData; |
| import org.eclipse.core.resources.IBuildConfiguration; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * <h1> C and C++ Project configuration and build helpers. </h1> |
| * <p> This class is focused on automating the process of enabling profiling plugins. <br> |
| * Used primarily for gcov gprof. </p> |
| * |
| * <p> It supports c/c++ in Managed and Autotools projects, <br> |
| * by providing the following functionality : <br> |
| * - Identify project type and differentiate C from C++ <br> |
| * - Check if a flag is set <br> |
| * - Enable a build-flag prog <br> |
| * - Rebuild a project </p> |
| * |
| * <p> Common steps: <br> |
| * <ol> |
| * <li> Check project type (Managed/Autotools) (getProjectType) </li> |
| * <li> Find out if it's C/C++, determine option ID to check (isCppType ..) </li> |
| * <li> Check if option (e.g -pg) is checked by the user (iOptionChecked ...) </li> |
| * <li> If not, prompt the user to have it checked. See {@link MessageDialogSyncedRunnable MessageDialogSyncedRunnable} ) </li> |
| * <li> If user agrees, programmatically set the option. (SetOptionIn ..) </li> |
| * <li> Rebuild the project </li> |
| * <li> Continue with launch. </li> |
| * </ol> |
| * For an example, see <code> org.eclipse.linuxtools.internal.gprof.launch.GprofLaunchConfigurationDelegate </code>. |
| * @since 3.1 |
| */ |
| public class CProjectBuildHelpers { |
| |
| /** |
| * <h1>Custom Project Type enumerator.</h1> |
| * |
| * <p> |
| * Used in conjunction with {@link CProjectBuildHelpers#getProjectType getProjectType} <br> |
| * Check the return value against these constants.<br> |
| * </p> |
| * |
| * <p> Can be set to one of the following: |
| * - AUTO_TOOLS <br> |
| * - MANAGED_MAKEFILE <br> |
| * - OTHER <br></p> |
| */ |
| public static enum ProjectBuildType { |
| AUTO_TOOLS, MANAGED_MAKEFILE, OTHER |
| } |
| |
| /** |
| * <h1>Finds out the type of the project as defined by {@link ProjectBuildType ProjectBuildType}.</h1> |
| * |
| * <p> A project can be of different types.<br> |
| * Common types are: |
| * - Autotools<br> |
| * - Managed Make project<br> |
| * - Manual Makefiles<br></p> |
| * |
| * |
| * <p> |
| * Some dialogues (initially in gCov and gProf) distinguish between these when displaying dialogues. This code is used |
| * by these dialogues. |
| * </p> |
| * |
| * <p> |
| * The method was written with extensibility in mind. <br> |
| * Other routines check for autotools/Managed make, and if those are not present, then it shows generic advice about |
| * adding flags. MAKEFILE per-se isn't really checked. I.e this means that it should be safe to add additional |
| * project types. |
| * </p> |
| * |
| * @param project project for which you want to get the type. |
| * @return (enum) projectType : <br> |
| * AUTO_TOOLS, <br> |
| * MANAGED_MAKEFILE, <br> |
| * OTHER <br> |
| */ |
| public static ProjectBuildType getProjectType(IProject project) { |
| // AUTOTOOLS |
| // Autotools has an 'Autotools' nature by which we can identify it. |
| if (isAutoTools(project)) { |
| return ProjectBuildType.AUTO_TOOLS; |
| } |
| |
| IConfiguration defaultConfiguration = helperGetActiveConfiguration(project); |
| IBuilder builder = defaultConfiguration.getBuilder(); |
| Boolean projIsManaged = builder.isManagedBuildOn(); |
| |
| // MANAGED PROJECT |
| if (projIsManaged) { |
| return ProjectBuildType.MANAGED_MAKEFILE; |
| } |
| else { |
| return ProjectBuildType.OTHER; //E.g a manual makefile. |
| } |
| } |
| |
| /** |
| * <h1>Differentiate C++ from C projects.</h1> |
| * |
| * <p> |
| * Projects can be c or cpp based.<br> |
| * Cpp projects have a ccnature as well as a cnature, <br> |
| * where as C projects only have a cnature. <br> |
| * </p> |
| * @param project the IProject project which will be read to check if it is c or cpp. |
| * @return true if it has a cpp nature. |
| */ |
| public static boolean isCppType(IProject project) { |
| try { |
| return project.hasNature(CCProjectNature.CC_NATURE_ID); |
| } catch (CoreException ex) { |
| //This should almost never happen unless the user manually edited the .project file. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetProjectType); |
| return false; |
| } |
| } |
| |
| /** |
| * <h1>Differentiates C from C++ projects.</h1> |
| * |
| * <p> |
| * Projects can be c or cpp based.<br> |
| * Cpp projects have a ccnature as well as a cnature, <br> |
| * where as C projects only have a cnature. |
| * </p> |
| * @param project the IProject project which will be read to check if it is c or cpp. |
| * @return true if it has a c nature BUT NOT a cpp nature. |
| */ |
| public static boolean isCType(IProject project) { |
| try { |
| // has C & has not CPP |
| return project.hasNature(CProjectNature.C_NATURE_ID) |
| && !project.hasNature(CCProjectNature.CC_NATURE_ID); |
| } catch (CoreException e) { |
| // should never really reach this. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetProjectType); |
| return false; |
| } |
| } |
| |
| /** |
| * <h1>Autotools projects have an Autotools Nature.</h1> |
| * |
| * @param project IProject that you're dealing with. |
| * @return true if the project has an autotools nature. |
| */ |
| public static boolean isAutoTools(IProject project) { |
| try { |
| return project.hasNature(AutotoolsNewProjectNature.AUTOTOOLS_NATURE_ID); |
| } catch (CoreException e) { |
| // should never really reach this unless .cproject is broken. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetProjectType); |
| return false; |
| } |
| } |
| |
| /** |
| * <h1>Check if an option is set in the CDT settings.</h1> |
| * |
| * <p> |
| * Commonly used when launching a plugin and you want to check if flags are set correctly. |
| * </p> |
| * |
| * <p> |
| * An example of an option id is: {@code gnu.cpp.compiler.option.debugging.gprof } To find the option id, check/uncheck it in |
| * your project. Then inspect the .cproject file. |
| * </p> |
| * |
| * <p> |
| * This method serves as a model to easily add similar methods. find what subclasses the iPrefernce store, it is |
| * likeley that your desired option is in one of those stores. |
| * </p> |
| * @param project the IProject project which will be read to check if it is c or cpp. |
| * @param optionIDString for example <code> gnu.cpp.compiler.option.debugging.codecov </code> |
| * @return true if the option is set. |
| */ |
| public static boolean isOptionCheckedInCDT(IProject project, String optionIDString) { |
| //Set Tool Name. |
| String toolSuperclassId = helperGetToolSuperClassId(project); |
| if (toolSuperclassId == null) { |
| return false; |
| } |
| return isOptionCheckedInCDTTool(project, optionIDString, toolSuperclassId); |
| } |
| |
| /** |
| * <h1>Check if an option is set</h1> |
| * Same as {@link #isOptionCheckedInCDT(IProject project, String optionIDString) isOptionChecked_inCDT }, |
| * except you specify tool name manually. <br> |
| * |
| * (e.g you need to check something that's not supported in the implementation above. |
| * @param project the IProject project which will be read to check if it is c or cpp |
| * @param optionIDString for example <code> gnu.cpp.compiler.option.debugging.codecov </code> |
| * @param toolSuperClassId superclass id of the parent tool. see {@link #helperGetToolSuperClassId helper_GetToolSuperClassId} |
| * @return true if the option is set |
| */ |
| private static boolean isOptionCheckedInCDTTool(IProject project, String optionIDString, String toolSuperClassId) { |
| |
| IConfiguration activeConf = helperGetActiveConfiguration(project); |
| |
| //Get Compiler tool. |
| ITool gccCompileriTool = helperGetGccCompilerToolBySuperClass(toolSuperClassId, activeConf); |
| |
| if (gccCompileriTool == null) { |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetProjectToolname); |
| return false; |
| } |
| |
| //(Get immutable option: This is like a 'template' that we will use to get the actual option) |
| IOption optionTemplate = gccCompileriTool.getOptionById(optionIDString); |
| |
| //Check that we got a good option. |
| if (optionTemplate == null) { |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetOptionTemplate); |
| return false; |
| } |
| |
| // Get Actual Option |
| // (Now we acquire the actual option from which we can read the value) |
| try { |
| IOption mutableOptionToSet = gccCompileriTool.getOptionToSet(optionTemplate, false); |
| return (boolean) mutableOptionToSet.getValue(); |
| } catch (BuildException e) { |
| //This is reached if the template that was provided was bad. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetOptionForWriting); |
| } |
| return false; |
| |
| } |
| |
| /** |
| * <h1> Enable a checkbox in the tools preference store and save to disk.</h1> |
| * <p> The tools prefernce store is where most compiler build flags are stored. <br> |
| * More specifically for 'debug' flags like gprof and gCov</p> |
| * |
| * <p>If you don't know how to get your IProject, see example: <br> |
| * <code> org.eclipse.linuxtools.internal.gprof.launch.GprofLaunchConfigurationDelegate.getProject() </code></p> |
| * |
| * <p>Related wiki: |
| * <a href="https://wiki.eclipse.org/CDT/Developer/Code_Snippets#Programmatically_set_an_option_in_the_project_settings"> |
| * Programmaticall check option wiki page. </a></p> |
| * |
| * @param project |
| * I project for which to set the flag |
| * @param optionIDString |
| * ID of option as defined in plugin.xml. e.g gnu.cpp.compiler.option.debugging.gprof |
| * @param value |
| * true or false |
| * @return false if something went wrong. True otherwise |
| */ |
| public static boolean setOptionInCDT(IProject project, String optionIDString, boolean value) { |
| // Set Tool Name. |
| String toolSuperClassId = helperGetToolSuperClassId(project); |
| if (toolSuperClassId == null) { |
| return false; |
| } |
| return setOptionInCDTTool(project, optionIDString, value, toolSuperClassId); |
| } |
| |
| /** |
| * <h1>Set Option in CDT</h1> |
| * Same as {@link #setOptionInCDT(IProject project, String optionIDString, boolean value) setOption_in } <br> |
| * except you can specify the parent tool manually (in case current implementation does not support what yon need. |
| * |
| * @param project an IProject |
| * @param optionIDString ID of option as defined in plugin.xml. e.g gnu.cpp.compiler.option.debugging.gprof |
| * @param value true/false |
| * @param toolSuperClassId |
| * Name of the tool where the option resides. E.g 'GCC C Compiler' or 'GCC C++ Compiler'. <br> |
| * To find out, check/uncheck an option, inspect the .cproject file, look for the option,<br> |
| * then see what tool it's under. See the name property |
| * @return true if all went well, false otherwise |
| */ |
| private static boolean setOptionInCDTTool(IProject project, String optionIDString, boolean value, String toolSuperClassId) { |
| |
| // Get configuration |
| IConfiguration activeConf = helperGetActiveConfiguration(project); |
| |
| // Get the ITool the option. |
| ITool gccCompileriTool = helperGetGccCompilerToolBySuperClass(toolSuperClassId, activeConf); |
| |
| // Get Template Opiton. |
| //Get Option ~Immutable. This is like a 'templete' that we will base the actual option on. |
| IOption optionTemplate = gccCompileriTool.getOptionById(optionIDString); |
| |
| //Check that we got a good option template. |
| if (optionTemplate == null) { |
| //This could fail if the specified option doesn't exist or is miss-spelled. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetOptionTemplate); |
| return false; |
| } |
| |
| // Get Actual Option |
| // |
| // Now we acquire an option that can be 'set' to something. |
| // In contrast to the immutable option above, if the user never checked/unchecked the option by hand, |
| // then the first time 'set' of this option will work correctly. Whereas |
| // the immutable option would only work if the user checked/unchecked the option by hand before. |
| IOption mutableOptionToSet = null; |
| try { |
| mutableOptionToSet = gccCompileriTool.getOptionToSet(optionTemplate, false); |
| mutableOptionToSet.setValue(value); |
| } catch (BuildException e) { |
| //This is reached if the template that was provided was bad. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetOptionForWriting); |
| } |
| |
| // get resource info. (where things are saved to). |
| IResourceInfo resourceInfo = activeConf.getResourceInfos()[0]; |
| |
| // Mark the option as enabled in the build manager. |
| ManagedBuildManager.setOption(resourceInfo, gccCompileriTool, mutableOptionToSet, |
| true); |
| |
| // Save this business to disk. |
| ManagedBuildManager.saveBuildInfo(project, true); |
| return true; |
| } |
| |
| /** |
| * <h1>Option enabled check</h1> |
| * <p> Check to see if an option is enabled in the .autotools configuration.</p> |
| * |
| * @param project the IProject project which will be read to check if it is c or cpp. |
| * @param optionId copy paste directly from .autotools. pick the 'ID' field value. |
| * @return true if it is |
| */ |
| public static boolean isOptionCheckedInAutotoolsPrefStore(final IProject project, final String optionId) { |
| |
| //We define a 'final' variable that will be accessible in the runnable object. |
| final BooleanWithGetSet userChoiceBool = new BooleanWithGetSet(false); |
| |
| // need to run this in the ui thread otherwise get SWT Exceptions |
| // based on concurrency issues. |
| Display.getDefault().syncExec(() -> { |
| |
| //Code copied from private methd: SetAutotoolsStringOptionValue.setOptionValue() |
| //Except I added a line to save the configuration to disk as well. |
| AutotoolsConfigurationManager.getInstance().syncConfigurations(project); |
| ICConfigurationDescription cfgds = CoreModel.getDefault() |
| .getProjectDescription(project).getActiveConfiguration(); |
| if (cfgds != null) { |
| IAConfiguration iaConfig = AutotoolsConfigurationManager.getInstance() |
| .getConfiguration(project, cfgds.getId()); |
| |
| //Read option value |
| IConfigureOption option = iaConfig.getOption(optionId); |
| String optValString = option.getValue(); |
| boolean optVal = Boolean.parseBoolean(optValString); |
| userChoiceBool.setVal(optVal); |
| } |
| }); |
| |
| return userChoiceBool.getVal(); |
| } |
| |
| /** |
| * <h1>Set Autotools option and write to disk.</h1> |
| * |
| * <p> Set an option (as well as flags) in the .autotools configuration and update gui. <br> |
| * It is oblivious as to whether the option ID is an option or a flag, it just looks at the ID in the xml. </p> |
| * |
| * <p> It is designed so that it can be ran from a background thread. |
| * It syncs with the GUI thread to avoid concurrency exceptions. </p> |
| * |
| * <p> *this modifies gui checkbox options* <b>as well as</b> *saving the option to disk*. </p> |
| * |
| * @param project the IProject project which will be read to check if it is c or cpp. |
| * @param optId Id of option to set. Take directly out of .autotools. a 'flag' is also an option. |
| * @param optVal string value of the option. e.g "true" "1234"; |
| */ |
| public static void setOptionInAutotools(final IProject project, final String optId, final String optVal) { |
| |
| // need to run this in the ui thread otherwise get SWT Exceptions |
| // based on concurrency issues. |
| Display.getDefault().syncExec(() -> { |
| |
| //Code copied from private methd: SetAutotoolsStringOptionValue.setOptionValue() |
| //Except I added a line to save the configuration to disk as well. |
| AutotoolsConfigurationManager.getInstance().syncConfigurations(project); |
| ICConfigurationDescription cfgds = CoreModel.getDefault(). |
| getProjectDescription(project).getActiveConfiguration(); |
| |
| if (cfgds != null) { |
| IAConfiguration iaConfig = AutotoolsConfigurationManager.getInstance() |
| .getConfiguration(project, cfgds.getId()); |
| |
| //Set option value. |
| iaConfig.setOption(optId, optVal); |
| |
| //Save option to disk. |
| AutotoolsConfigurationManager.getInstance().saveConfigs(project); |
| } |
| }); |
| } |
| |
| /** |
| * <h1>Trigger a re-build of the project.</h1> |
| * |
| * <p> Given a project, it finds the active configuration and rebuilds the project. </p> |
| * |
| * <p> This works with C/C++ Managed Builds as well as Autotools.</p> |
| * |
| * <p>Most useful for when you have added a flag to a project programmatically and want to rebuild |
| * the project so that the program is rebuild with that flag. The Rebuild triggers an update of the makefile |
| * automatically.</p> |
| * |
| * @param project to rebuild |
| */ |
| public static void rebuildProject(IProject project) { |
| //rebuild does not generate an analysis file (e.g gmon.out) But since -pg flag is added, profiling support is added to the binary. |
| try { |
| IBuildConfiguration buildConfiguration = project.getActiveBuildConfig(); |
| |
| //force a full rebuild which is usually needed for when you add profiling flags to the build options. |
| project.build(buildConfiguration, IncrementalProjectBuilder.FULL_BUILD, null); |
| } catch (CoreException e) { |
| |
| //This is a very rare occurrence. Usually rebuilds don't fail if they worked the first time. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorRebuilding); |
| } |
| } |
| |
| /* |
| * PRIVATE HELPERS BELOW |
| */ |
| |
| /** |
| * <p>Helper to get the active build configuration.</p> |
| * @param project IProject for which to get the configuration. |
| * @return IConfiguration of that project. |
| */ |
| @SuppressWarnings("restriction") |
| private static IConfiguration helperGetActiveConfiguration(IProject project) { |
| ICConfigurationDescription cfgd = CoreModel.getDefault() |
| .getProjectDescription(project, false) |
| .getActiveConfiguration(); |
| |
| IConfiguration cfg = null; |
| |
| try { |
| if (cfgd instanceof CConfigurationDescriptionCache) { |
| CConfigurationData data = ((CConfigurationDescriptionCache) cfgd) |
| .getConfigurationData(); |
| if (data instanceof BuildConfigurationData) { |
| cfg = ((BuildConfigurationData) data).getConfiguration(); |
| } |
| } |
| |
| if (cfg == null) { |
| IManagedBuildInfo buildInfo = ManagedBuildManager.getBuildInfo(project); |
| if (buildInfo != null) { |
| return buildInfo.getDefaultConfiguration(); |
| } |
| } |
| } catch (Exception e) { |
| // ignore |
| } |
| return cfg; |
| } |
| |
| /** |
| * <p> For a project, calculate the expected superclass id of the compiler tool we expect the compiler tool to be under.</p> |
| * |
| * @param project IProject for which to get the tool superclass id. |
| * @return superclass id of the parent tool. |
| */ |
| private static String helperGetToolSuperClassId(IProject project) { |
| String superClassId = null; |
| if (isCType(project)) { |
| superClassId = "cdt.managedbuild.tool.gnu.c.compiler"; //$NON-NLS-1$ |
| } else if (isCppType(project)) { |
| superClassId = "cdt.managedbuild.tool.gnu.cpp.compiler"; //$NON-NLS-1$ |
| } else { |
| //This should happen only if project natures are broken. Maybe the .project file is corrupted. |
| MessageDialogSyncedRunnable.openErrorSyncedRunnable(ProfilingMessages.errorTitle, ProfilingMessages.errorGetProjectToolname); |
| return null; |
| } |
| return superClassId; |
| } |
| |
| /** |
| * <h1>Get the tool that has the given id at the top of its superclass chain.</h1> |
| * |
| * @param superClassId a string representing the expected top-most superclass id of the compiler tool. |
| * @param activeConf The current active configuration of the project, from which we should be able to find the ITool. |
| * @return the 'ITool' instance. |
| */ |
| private static ITool helperGetGccCompilerToolBySuperClass(String superClassId, IConfiguration activeConf) { |
| ITool[] tools = activeConf.getTools(); |
| ITool gccCompileriTool = null; |
| for (ITool iTool : tools) { |
| ITool tool = iTool; |
| while (tool.getSuperClass() != null) { |
| tool = tool.getSuperClass(); |
| } |
| if (tool.getId().equals(superClassId)) { |
| gccCompileriTool = iTool; |
| break; |
| } |
| } |
| return gccCompileriTool; |
| } |
| |
| } |