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 }