blob: 29d25fc514e0836ee56ed18ac1c21e2ca4d1a0dd [file] [log] [blame]
/*******************************************************************************
* 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;
}
}