/******************************************************************************* * Copyright (c) 2016 Xilinx, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Xilinx - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.internal.cdt.ui; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; import org.eclipse.cdt.core.settings.model.ICProjectDescription; import org.eclipse.cdt.launch.LaunchUtils; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.tcf.debug.ITCFLaunchProjectBuilder; import org.eclipse.tcf.internal.debug.launch.TCFLaunchDelegate; /** * Build CDT project. * This is an specialization of the default project build logic. * Unfortunately, CDT does not profile an API for building a project. See bug 313927. * See org.eclipse.cdt.launch.AbstractCLaunchDelegate2 for more details. */ public class TCFLaunchProjectBuilder implements ITCFLaunchProjectBuilder { @Override public boolean isSupportedProject(String name) { IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); return CCorePlugin.getDefault().getCoreModel().create(project) != null; } @Override public IProject[] getBuildOrder(ILaunchConfiguration configuration, String mode) throws CoreException { String name = configuration.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_NAME, ""); if (name.length() == 0) return null; IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); if (project == null) return null; ICProject c_project = CCorePlugin.getDefault().getCoreModel().create(project); if (c_project == null) return null; IProject[] ordered_projects = null; HashSet<IProject> project_set = new HashSet<IProject>(); getReferencedProjectSet(c_project.getProject(), project_set); String[] ordered_names = ResourcesPlugin.getWorkspace().getDescription().getBuildOrder(); if (ordered_names != null) { // Projects may not be in the build order but should still be built if selected ArrayList<IProject> unordered_list = new ArrayList<IProject>(project_set.size()); ArrayList<IProject> ordered_list = new ArrayList<IProject>(project_set.size()); unordered_list.addAll(project_set); for (String nm : ordered_names) { for (IProject proj : unordered_list) { if (proj.getName().equals(nm)) { ordered_list.add(proj); unordered_list.remove(proj); break; } } } // Add any remaining projects to the end of the list ordered_list.addAll(unordered_list); ordered_projects = ordered_list.toArray(new IProject[ordered_list.size()]); } else { // Try the project prerequisite order then IProject[] projects = project_set.toArray(new IProject[project_set.size()]); ordered_projects = ResourcesPlugin.getWorkspace().computeProjectOrder(projects).projects; } return ordered_projects; } /* Recursively creates a set of projects referenced by a project */ private void getReferencedProjectSet(IProject proj, Set<IProject> set) throws CoreException { set.add(proj); for (IProject ref : proj.getReferencedProjects()) { if (ref.exists() && !set.contains(ref)) { getReferencedProjectSet(ref, set); } } } /** * Builds the project referenced in the launch configuration */ public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException { try { SubMonitor submon = SubMonitor.convert(monitor, "", 1); //$NON-NLS-1$ String name = configuration.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_NAME, ""); if (name.length() == 0) return true; IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); if (project == null) return true; String buildConfigID = null; if (configuration.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_BUILD_CONFIG_AUTO, false)) { String program_path = configuration.getAttribute(TCFLaunchDelegate.ATTR_LOCAL_PROGRAM_FILE, ""); //$NON-NLS-1$ program_path = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(program_path); ICConfigurationDescription buildConfig = LaunchUtils.getBuildConfigByProgramPath(project, program_path); if (buildConfig != null) buildConfigID = buildConfig.getId(); } if (buildConfigID == null) buildConfigID = configuration.getAttribute(TCFLaunchDelegate.ATTR_PROJECT_BUILD_CONFIG_ID, (String)null); // There's no guarantee the ID stored in the launch config is valid. // The user may have deleted the build configuration. if (buildConfigID != null) { boolean idIsGood = false; ICProjectDescription desc = CCorePlugin.getDefault().getProjectDescription(project, false); if (desc != null) idIsGood = desc.getConfigurationById(buildConfigID) != null; if (!idIsGood) buildConfigID = null; // use active configuration } buildProject(project, buildConfigID, submon.newChild(1)); return false; } finally { if (monitor != null) monitor.done(); } } /** * This is an specialization of the platform method * LaunchConfigurationDelegate#buildProjects(IProject[], IProgressMonitor). * It builds only one project and it builds a particular CDT build * configuration of it. It was added to address bug 309126 and 312709 */ public void buildProject(final IProject project, final String buildConfigID, IProgressMonitor monitor) throws CoreException { final int TOTAL_TICKS = 1000; // Some day, this will hopefully be a simple pass-thru to a cdt.core // utility. See bug 313927 IWorkspaceRunnable build = new IWorkspaceRunnable(){ @Override public void run(IProgressMonitor pm) throws CoreException { SubMonitor localmonitor = SubMonitor.convert(pm, "", TOTAL_TICKS); //$NON-NLS-1$ try { // Number of times we'll end up calling IProject.build() final int buildCount = (buildConfigID == null) ? 1 : project.getDescription().getBuildSpec().length; if (buildCount == 0) return; // the case for an imported-executable project; see bugzilla 315396 final int subtaskTicks = TOTAL_TICKS / buildCount; if (buildConfigID != null) { // Build a specific configuration // To pass args, we have to specify the builder name. // There can be multiple so this can require multiple // builds. Note that this happens under the covers in // the 'else' (args-less) case below Map<String,String> cfgIdArgs = cfgIdsToMap(new String[] {buildConfigID}, new HashMap<String,String>()); cfgIdArgs.put(CONTENTS, CONTENTS_CONFIGURATION_IDS); ICommand[] commands = project.getDescription().getBuildSpec(); assert buildCount == commands.length; for (ICommand command : commands) { Map<String, String> args = command.getArguments(); if (args == null) { args = new HashMap<String, String>(cfgIdArgs); } else { args.putAll(cfgIdArgs); } if (localmonitor.isCanceled()) { throw new OperationCanceledException(); } project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, command.getBuilderName(), args, localmonitor.newChild(subtaskTicks)); } } else { // Build the active configuration project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, localmonitor.newChild(subtaskTicks)); } } finally { if (pm != null) pm.done(); } } }; ResourcesPlugin.getWorkspace().run(build, new SubProgressMonitor(monitor, TOTAL_TICKS, 0) { private boolean cancelled; @Override public void setCanceled(boolean b) { // Only cancel this operation, not the top-level launch. cancelled = b; } @Override public boolean isCanceled() { // Canceled if this monitor has been explicitly canceled // || parent has been canceled. return cancelled || super.isCanceled(); } }); } /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static final String CONFIGURATION_IDS = "org.eclipse.cdt.make.core.configurationIds"; //$NON-NLS-1$ /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static final String CONTENTS = "org.eclipse.cdt.make.core.contents"; //$NON-NLS-1$ /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static final String CONTENTS_CONFIGURATION_IDS = "org.eclipse.cdt.make.core.configurationIds"; //$NON-NLS-1$ /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static Map<String, String> cfgIdsToMap(String ids[], Map<String, String> map){ map.put(CONFIGURATION_IDS, encodeList(Arrays.asList(ids))); return map; } /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static String encodeList(List<String> values) { StringBuffer str = new StringBuffer(); Iterator<String> entries = values.iterator(); while (entries.hasNext()) { String entry = entries.next(); str.append(escapeChars(entry, "|\\", '\\')); //$NON-NLS-1$ str.append("|"); //$NON-NLS-1$ } return str.toString(); } /** TODO: Temporarily duplicated from BuilderFactory. Remove when 313927 is addressed */ private static String escapeChars(String string, String escapeChars, char escapeChar) { StringBuffer str = new StringBuffer(string); for (int i = 0; i < str.length(); i++) { if (escapeChars.indexOf(str.charAt(i)) != -1) { str.insert(i, escapeChar); i++; } } return str.toString(); } }