1 /******************************************************************************* 2 * Copyright (c) 2000, 2013 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Matthew Conway - Bug 175186 14 *******************************************************************************/ 15 package org.eclipse.core.externaltools.internal.model; 16 17 18 import java.util.Map; 19 20 import org.eclipse.core.externaltools.internal.ExternalToolsCore; 21 import org.eclipse.core.externaltools.internal.IExternalToolConstants; 22 import org.eclipse.core.externaltools.internal.launchConfigurations.ExternalToolsCoreUtil; 23 import org.eclipse.core.externaltools.internal.registry.ExternalToolMigration; 24 import org.eclipse.core.resources.ICommand; 25 import org.eclipse.core.resources.IFile; 26 import org.eclipse.core.resources.IProject; 27 import org.eclipse.core.resources.IProjectDescription; 28 import org.eclipse.core.resources.IResource; 29 import org.eclipse.core.resources.IResourceDelta; 30 import org.eclipse.core.resources.IResourceDeltaVisitor; 31 import org.eclipse.core.resources.IncrementalProjectBuilder; 32 import org.eclipse.core.runtime.CoreException; 33 import org.eclipse.core.runtime.IPath; 34 import org.eclipse.core.runtime.IProgressMonitor; 35 import org.eclipse.debug.core.ILaunchConfiguration; 36 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; 37 import org.eclipse.debug.core.ILaunchManager; 38 import org.eclipse.osgi.util.NLS; 39 import org.osgi.framework.Bundle; 40 41 /** 42 * This project builder implementation will run an external tool during the 43 * build process. 44 */ 45 public final class ExternalToolBuilder extends IncrementalProjectBuilder { 46 private final class IgnoreTeamPrivateChanges implements IResourceDeltaVisitor { 47 private boolean[] fTrueChange; IgnoreTeamPrivateChanges(boolean[] trueChange)48 private IgnoreTeamPrivateChanges(boolean[] trueChange) { 49 super(); 50 fTrueChange= trueChange; 51 } 52 @Override visit(IResourceDelta visitDelta)53 public boolean visit(IResourceDelta visitDelta) throws CoreException { 54 IResource resource= visitDelta.getResource(); 55 if (resource instanceof IFile) { 56 fTrueChange[0]= true; 57 return false; 58 } 59 return true; 60 } 61 } 62 63 public static final String ID = "org.eclipse.ui.externaltools.ExternalToolBuilder"; //$NON-NLS-1$; 64 65 private static String buildType = IExternalToolConstants.BUILD_TYPE_NONE; 66 67 private static IProject buildProject= null; 68 private static IResourceDelta buildDelta= null; 69 70 @Override build(int kind, Map<String, String> args, IProgressMonitor monitor)71 protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { 72 if (ExternalToolsCore.getDefault().getBundle().getState() != Bundle.ACTIVE) { 73 return null; 74 } 75 76 ILaunchConfiguration config= BuilderCoreUtils.configFromBuildCommandArgs(getProject(), args, new String[1]); 77 if (config == null) { 78 throw ExternalToolsCore.newError(ExternalToolsModelMessages.ExternalToolBuilder_0, null); 79 } 80 IProject[] projectsWithinScope= null; 81 IResource[] resources = ExternalToolsCoreUtil.getResourcesForBuildScope(config); 82 if (resources != null) { 83 projectsWithinScope= new IProject[resources.length]; 84 for (int i = 0; i < resources.length; i++) { 85 projectsWithinScope[i]= resources[i].getProject(); 86 } 87 } 88 boolean kindCompatible= commandConfiguredForKind(config, kind); 89 if (kindCompatible && configEnabled(config)) { 90 doBuildBasedOnScope(resources, kind, config, args, monitor); 91 } 92 93 return projectsWithinScope; 94 } 95 commandConfiguredForKind(ILaunchConfiguration config, int kind)96 private boolean commandConfiguredForKind(ILaunchConfiguration config, int kind) { 97 try { 98 if (!(config.getAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, false))) { 99 ICommand command= getCommand(); 100 //adapt the builder command to make use of the 3.1 support for setting command build kinds 101 //this will only happen once for builder/command defined before the support existed 102 BuilderCoreUtils.configureTriggers(config, command); 103 IProjectDescription desc= getProject().getDescription(); 104 ICommand[] commands= desc.getBuildSpec(); 105 int index= getBuilderCommandIndex(commands, command); 106 if (index != -1) { 107 commands[index]= command; 108 desc.setBuildSpec(commands); 109 getProject().setDescription(desc, null); 110 ILaunchConfigurationWorkingCopy copy= config.getWorkingCopy(); 111 copy.setAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, true); 112 copy.doSave(); 113 } 114 return command.isBuilding(kind); 115 } 116 } catch (CoreException e) { 117 ExternalToolsCore.log(e); 118 return true; 119 } 120 return true; 121 } 122 getBuilderCommandIndex(ICommand[] buildSpec, ICommand command)123 private int getBuilderCommandIndex(ICommand[] buildSpec, ICommand command) { 124 Map<String, String> commandArgs = command.getArguments(); 125 if (commandArgs == null) { 126 return -1; 127 } 128 String handle= commandArgs.get(BuilderCoreUtils.LAUNCH_CONFIG_HANDLE); 129 if (handle == null) { 130 return -1; 131 } 132 for (int i = 0; i < buildSpec.length; ++i) { 133 ICommand buildSpecCommand= buildSpec[i]; 134 if (ID.equals(buildSpecCommand.getBuilderName())) { 135 Map<String, String> buildSpecArgs = buildSpecCommand.getArguments(); 136 if (buildSpecArgs != null) { 137 String buildSpecHandle= buildSpecArgs.get(BuilderCoreUtils.LAUNCH_CONFIG_HANDLE); 138 if (handle.equals(buildSpecHandle)) { 139 return i; 140 } 141 } 142 } 143 } 144 return -1; 145 } 146 147 /** 148 * Returns whether the given builder config is enabled or not. 149 * 150 * @param config the config to examine 151 * @return whether the config is enabled 152 */ configEnabled(ILaunchConfiguration config)153 private boolean configEnabled(ILaunchConfiguration config) { 154 try { 155 return ExternalToolsCoreUtil.isBuilderEnabled(config); 156 } catch (CoreException e) { 157 ExternalToolsCore.log(e); 158 } 159 return true; 160 } 161 doBuildBasedOnScope(IResource[] resources, int kind, ILaunchConfiguration config, Map<String, String> args, IProgressMonitor monitor)162 private void doBuildBasedOnScope(IResource[] resources, int kind, ILaunchConfiguration config, Map<String, String> args, IProgressMonitor monitor) throws CoreException { 163 boolean buildForChange = true; 164 if (kind != FULL_BUILD) { //scope not applied for full builds 165 if (resources != null && resources.length > 0) { 166 buildForChange = buildScopeIndicatesBuild(resources); 167 } 168 } 169 170 if (buildForChange) { 171 launchBuild(kind, config, args, monitor); 172 } 173 } 174 launchBuild(int kind, ILaunchConfiguration config, Map<String, String> args, IProgressMonitor monitor)175 private void launchBuild(int kind, ILaunchConfiguration config, Map<String, String> args, IProgressMonitor monitor) throws CoreException { 176 monitor.subTask(NLS.bind(ExternalToolsModelMessages.ExternalToolBuilder_Running__0_____1, new String[] { config.getName()})); 177 buildStarted(kind, args); 178 // The default value for "launch in background" is true in debug core. If 179 // the user doesn't go through the UI, the new attribute won't be set. This means 180 // that existing Ant builders will try to run in the background (and likely conflict with 181 // each other) without migration. 182 ILaunchConfiguration newconfig= ExternalToolMigration.migrateRunInBackground(config); 183 newconfig.launch(ILaunchManager.RUN_MODE, monitor); 184 buildEnded(); 185 } 186 187 /** 188 * Returns the build type being performed if the 189 * external tool is being run as a project builder. 190 * 191 * @return one of the <code>IExternalToolConstants.BUILD_TYPE_*</code> constants. 192 */ getBuildType()193 public static String getBuildType() { 194 return buildType; 195 } 196 197 /** 198 * Returns the project that is being built and has triggered the current external 199 * tool builder. <code>null</code> is returned if no build is currently occurring. 200 * 201 * @return project being built or <code>null</code>. 202 */ getBuildProject()203 public static IProject getBuildProject() { 204 return buildProject; 205 } 206 207 /** 208 * Returns the <code>IResourceDelta</code> that is being built and has triggered the current external 209 * tool builder. <code>null</code> is returned if no build is currently occurring. 210 * 211 * @return resource delta for the build or <code>null</code> 212 */ getBuildDelta()213 public static IResourceDelta getBuildDelta() { 214 return buildDelta; 215 } 216 217 /** 218 * Stores the currently active build kind and build project when a build begins 219 * @param buildKind 220 * @param args the arguments passed into the builder 221 */ buildStarted(int buildKind, Map<String, String> args)222 private void buildStarted(int buildKind, Map<String, String> args) { 223 switch (buildKind) { 224 case IncrementalProjectBuilder.INCREMENTAL_BUILD : 225 buildType = IExternalToolConstants.BUILD_TYPE_INCREMENTAL; 226 buildDelta = getDelta(getProject()); 227 break; 228 case IncrementalProjectBuilder.FULL_BUILD : 229 if(args != null && args.containsKey(BuilderCoreUtils.INC_CLEAN)) { 230 buildType = IExternalToolConstants.BUILD_TYPE_INCREMENTAL; 231 buildDelta = getDelta(getProject()); 232 } 233 else { 234 buildType = IExternalToolConstants.BUILD_TYPE_FULL; 235 } 236 break; 237 case IncrementalProjectBuilder.AUTO_BUILD : 238 buildType = IExternalToolConstants.BUILD_TYPE_AUTO; 239 buildDelta = getDelta(getProject()); 240 break; 241 case IncrementalProjectBuilder.CLEAN_BUILD : 242 buildType = IExternalToolConstants.BUILD_TYPE_CLEAN; 243 break; 244 default : 245 buildType = IExternalToolConstants.BUILD_TYPE_NONE; 246 break; 247 } 248 buildProject= getProject(); 249 } 250 251 /** 252 * Clears the current build kind, build project and build delta when a build finishes. 253 */ buildEnded()254 private void buildEnded() { 255 buildType= IExternalToolConstants.BUILD_TYPE_NONE; 256 buildProject= null; 257 buildDelta= null; 258 } 259 buildScopeIndicatesBuild(IResource[] resources)260 private boolean buildScopeIndicatesBuild(IResource[] resources) { 261 for (IResource resource : resources) { 262 IResourceDelta delta = getDelta(resource.getProject()); 263 if (delta == null) { 264 //project just added to the workspace..no previous build tree 265 return true; 266 } 267 IPath path = resource.getProjectRelativePath(); 268 IResourceDelta change= delta.findMember(path); 269 if (change != null) { 270 final boolean[] trueChange= new boolean[1]; 271 trueChange[0]= false; 272 try { 273 change.accept(new IgnoreTeamPrivateChanges(trueChange)); 274 } catch (CoreException e) { 275 ExternalToolsCore.log("Internal error resolving changed resources during build", e); //$NON-NLS-1$ 276 } 277 278 return trueChange[0]; //filtered out team private changes 279 } 280 } 281 return false; 282 } 283 284 @Override clean(IProgressMonitor monitor)285 protected void clean(IProgressMonitor monitor) throws CoreException { 286 ICommand command= getCommand(); 287 ILaunchConfiguration config= BuilderCoreUtils.configFromBuildCommandArgs(getProject(), command.getArguments(), new String[1]); 288 if (!configEnabled(config)) { 289 return; 290 } 291 292 if ((!config.getAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, false))) { 293 //old behavior 294 super.clean(monitor); 295 return; 296 } 297 298 launchBuild(IncrementalProjectBuilder.CLEAN_BUILD, config, null, monitor); 299 } 300 }