Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: f0c7959e35d94badb66a308a7fb9393283b11ee6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
/*******************************************************************************
 * Copyright (c) 2014, 2015 Red Hat, Inc.
 * 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:
 *    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.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.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.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 {
			if (project.hasNature(CCProjectNature.CC_NATURE_ID)) {
				return true;
			}
			else {
				return false;
			}
		} 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
			if (project.hasNature(CProjectNature.C_NATURE_ID)
					&& !project.hasNature(CCProjectNature.CC_NATURE_ID)) {
				return true;
			} else {
				return false;
			}
		} 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 {
			if (project.hasNature(AutotoolsNewProjectNature.AUTOTOOLS_NATURE_ID)) { // this guy throws.
				return true;
			} else {
				return false;
			}
		} 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);

		//(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(new Runnable() {
			@Override
			public void run() {

				//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(new Runnable() {
			@Override
			public void run() {

				//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.
	 */
	private static IConfiguration helperGetActiveConfiguration(IProject project) {
		IManagedBuildInfo buildInfo = ManagedBuildManager.getBuildInfo(project);
		return buildInfo.getDefaultConfiguration();
	}

	/**
	 * <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;
	}

}

Back to the top