Bug 579082 - new API for IncrementalProjectBuilder for rebuilding

Added new requestProjectRebuild(boolean) and
requestProjectsRebuild(Collection<IProject>) API in
IncrementalProjectBuilder that allows builders that generate sources
relevant for the current project or to any given projects to trigger
extra rebuild round, so other builders on same project or other projects
build can be re-triggered in the current build job.

The default IncrementalProjectBuilder implementation uses new methods
requestRebuild(Collection<IProject>) and requestRebuild(IProject,
boolean) in BuildManager.

If requestProjectRebuild(boolean) is called by a builder during project
build, BuildManager will try to rebuild current project only. The
boolean flag allows builders to request immediate project rebuild (no
following builders will be run), or to do a rebuild after all configured
builders are done.

If requestProjectsRebuild(Collection<IProject>) is called by a
builder, BuildManager will try to rebuild given projects in the next
build cycle.

Additionally to that, a new system property allows to skip other
projects to be built and restart entire build loop from scratch, if a
rebuild was requested via needRebuild() or
requestRebuild(Collection<IProject>). This can be done by setting system
property
-Dorg.eclipse.core.resources.allowEarlyBuildLoopExit=true

Change-Id: Ibbe9181531a89ed6fc436e4bc26f6e0d0b7fb4bd
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/191538
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
index 0ab8793..8d19984 100644
--- a/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true
-Bundle-Version: 3.16.200.qualifier
+Bundle-Version: 3.17.0.qualifier
 Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
index f75a60a..aeca267 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java
@@ -20,6 +20,7 @@
 package org.eclipse.core.internal.events;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.eclipse.core.internal.dtree.DeltaDataTree;
@@ -132,17 +133,33 @@
 
 	/**
 	 * {@code true} if we can exit inner build loop cycle early after
-	 * rebuildRequested is set by one build config and before following build
-	 * configs are executed. Default is {@code true}.
+	 * {@link #requestRebuild()} is set by one build config and before following
+	 * build configs are executed. Default is {@code false}.
 	 */
-	private static final boolean EARLY_EXIT_FROM_INNER_BUILD_LOOP_ALLOWED = System
-			.getProperty("org.eclipse.core.resources.disallowEarlyInnerBuildLoopExit") == null; //$NON-NLS-1$
+	private boolean earlyExitFromBuildLoopAllowed;
 
-	//used for the build cycle looping mechanism
-	private boolean rebuildRequested = false;
+	/**
+	 * Used for the build cycle looping mechanism. If true, build loop over multiple
+	 * projects will be restarted again for all projects in the loop
+	 */
+	private boolean rebuildRequested;
+
+	/**
+	 * Set of projects for which builders requested rebuild. Has no effect if any
+	 * builder requested rebuild of everything via {@link #rebuildRequested}
+	 */
+	private final Set<IProject> projectsToRebuild;
+
+	/**
+	 * Map of projects for which builders requested rebuild for the current build
+	 * cycle. If the value is "true" - stop building project with other builders
+	 * immediately, "false" to continue build and start project build again after
+	 * all builders were done. If no value is set, no rebuild is requested.
+	 */
+	private final Map<IProject, Boolean> restartBuildImmediately;
 
 	// Shows if we are in the parallel build loop or not
-	private boolean parallelBuild;
+	boolean parallelBuild;
 
 	private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
 
@@ -158,8 +175,12 @@
 		this.workspace = workspace;
 		this.currentBuilders = Collections.synchronizedSet(new HashSet<>());
 		this.autoBuildJob = new AutoBuildJob(workspace);
+		projectsToRebuild = ConcurrentHashMap.newKeySet();
+		restartBuildImmediately = new ConcurrentHashMap<>();
 		this.lock = workspaceLock;
 		InternalBuilder.buildManager = this;
+		setEarlyExitFromBuildLoopAllowed(
+				Boolean.getBoolean("org.eclipse.core.resources.allowEarlyBuildLoopExit")); //$NON-NLS-1$ );
 	}
 
 	private void basicBuild(int trigger, IncrementalProjectBuilder builder, Map<String, String> args, MultiStatus status, IProgressMonitor monitor) {
@@ -265,20 +286,41 @@
 	}
 
 	protected void basicBuild(IBuildConfiguration buildConfiguration, int trigger, IBuildContext context, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
+		int remainingIterations = Math.max(1, workspace.getDescription().getMaxBuildIterations());
+
 		try {
-			for (int i = 0; i < commands.length; i++) {
-				checkCanceled(trigger, monitor);
-				if (EARLY_EXIT_FROM_INNER_BUILD_LOOP_ALLOWED && rebuildRequested && !parallelBuild
-						&& workspace.isAutoBuilding()) {
-					// Don't build following configs if one of the predecessors
-					// requested rebuild anyway, just start from scratch
-					break;
+			boolean shouldRebuild = true;
+			while (shouldRebuild) {
+				shouldRebuild = false;
+				for (int i = 0; i < commands.length; i++) {
+					checkCanceled(trigger, monitor);
+					BuildCommand command = (BuildCommand) commands[i];
+					IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
+					IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context);
+					if (builder != null) {
+						basicBuild(trigger, builder, command.getArguments(false), status, sub);
+
+						// Check if the builder requested rebuild
+						IProject project = builder.getProject();
+						Boolean restartImmediately = restartBuildImmediately.remove(project);
+						if (restartImmediately != null) {
+							remainingIterations--;
+							if (remainingIterations > 0) {
+								if (!restartImmediately) {
+									// process building all builders and restart after that
+									shouldRebuild = true;
+								} else {
+									// First builder doesn't need to restart anything
+									if (i > 0) {
+										// Start for loop again, input can be important for all builders before
+										shouldRebuild = true;
+										break;
+									}
+								}
+							}
+						}
+					}
 				}
-				BuildCommand command = (BuildCommand) commands[i];
-				IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
-				IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context);
-				if (builder != null)
-					basicBuild(trigger, builder, command.getArguments(false), status, sub);
 			}
 		} catch (CoreException e) {
 			status.add(e.getStatus());
@@ -380,19 +422,37 @@
 		// Scale allowed iterations count depending on affected projects -
 		// allow at least two build cycles per project
 		maxIterations = Math.max(configs.length * 2, maxIterations);
-		if (maxIterations <= 0)
+		if (maxIterations <= 0) {
 			maxIterations = 1;
+		}
+
 		rebuildRequested = true;
-		for (int iter = 0; rebuildRequested && iter < maxIterations; iter++) {
-			rebuildRequested = false;
-			builtProjects.clear();
-			for (IBuildConfiguration config : configs) {
-				if (config.getProject().isAccessible()) {
-					IBuildContext context = new BuildContext(config, requestedConfigs, configs);
-					basicBuild(config, trigger, context, status, Policy.subMonitorFor(monitor, projectWork));
-					builtProjects.add(config.getProject());
-				}
+		boolean rebuildSomething = true;
+		for (int iter = 0; rebuildSomething && iter < maxIterations; iter++) {
+			// Used for compatibility reason with requestRebuild()
+			boolean rebuildAll = rebuildRequested;
+			final boolean lastIteration = iter == maxIterations - 1;
+
+			if (rebuildAll) {
+				// default build loop
+				basicBuildLoop(configs, requestedConfigs, trigger, status, monitor, projectWork, lastIteration);
+			} else {
+				// rebuild only projects requested by builders during previous build cycle
+				List<IBuildConfiguration> allConfigs = Arrays.asList(workspace.getBuildOrder());
+				IBuildConfiguration[] configurations = allConfigs.stream()
+						.filter(c -> projectsToRebuild.contains(c.getProject())).toArray(IBuildConfiguration[]::new);
+				basicBuildLoop(configurations, requestedConfigs, trigger, status, monitor, projectWork, lastIteration);
 			}
+			if (rebuildRequested) {
+				rebuildSomething = true;
+				projectsToRebuild.clear();
+				restartBuildImmediately.clear();
+			} else if (!projectsToRebuild.isEmpty()) {
+				rebuildSomething = true;
+			} else {
+				rebuildSomething = false;
+			}
+
 			// subsequent builds should always be incremental
 			// i.e. autobuild if not requested by user
 			// INCREMENTAL_BUILD would not be auto interrupted by user actions
@@ -402,6 +462,49 @@
 		}
 	}
 
+	private void basicBuildLoop(IBuildConfiguration[] configs, IBuildConfiguration[] requestedConfigs, int trigger,
+			MultiStatus status, IProgressMonitor monitor, int projectWork, final boolean lastIteration) {
+
+		// If we are rebuilding anything, we can clear already build projects.
+		// If we build only few dedicated projects, all others, already built
+		// projects should be added again to the "built" list, otherwise
+		// hasBeenBuilt() will return "false" for them and they would not considered
+		// for a rebuild if requested by one of the projects to be re-built now.
+		if (rebuildRequested) {
+			builtProjects.clear();
+		} else {
+			builtProjects.removeAll(projectsToRebuild);
+		}
+
+		// Clear all the rebuild related flags before entering new build cycle
+		rebuildRequested = false;
+		projectsToRebuild.clear();
+		restartBuildImmediately.clear();
+
+		// Basic loop over projects
+		for (IBuildConfiguration config : configs) {
+			if (config.getProject().isAccessible()) {
+				IBuildContext context = new BuildContext(config, requestedConfigs, configs);
+
+				// Inner loop over builders in one project
+				basicBuild(config, trigger, context, status, Policy.subMonitorFor(monitor, projectWork));
+				builtProjects.add(config.getProject());
+
+				// Check if we should continue with other projects
+				if ((rebuildRequested || !projectsToRebuild.isEmpty())
+						&& isEarlyExitFromBuildLoopAllowed()) {
+					if (lastIteration) {
+						// run build for all projects at least once
+						continue;
+					}
+					// Don't build following projects if one of the predecessors
+					// requested rebuild anyway, just start main loop from scratch
+					break;
+				}
+			}
+		}
+	}
+
 	/**
 	 * Runs all builders on all the given project configs, in the order that
 	 * they are given.
@@ -490,6 +593,8 @@
 	public IStatus build(IBuildConfiguration buildConfiguration, int trigger, String builderName, Map<String, String> args, IProgressMonitor monitor) {
 		monitor = Policy.monitorFor(monitor);
 		rebuildRequested = false;
+		projectsToRebuild.clear();
+		restartBuildImmediately.clear();
 		if (builderName == null) {
 			IBuildContext context = new BuildContext(buildConfiguration);
 			return basicBuild(buildConfiguration, trigger, context, monitor);
@@ -1192,6 +1297,58 @@
 	}
 
 	/**
+	 * Hook for builders to request a rebuild for given project during the current
+	 * build call. The builders configured to run after the current one will be
+	 * still processed. To force an immediate rebuild of a project that wasn't fully
+	 * built yet, {@code processOtherBuilders} argument should be set to
+	 * {@code false}.
+	 * <p>
+	 * <b>Note</b> if {@code processOtherBuilders} is set to {@code false}, the
+	 * project that is built with current builder will be only rebuilt again, if
+	 * this builder is not the first one configured to run.
+	 *
+	 * @param processOtherBuilders to continue building project with other builders
+	 *                             and not start from scratch immediately
+	 */
+	void requestRebuild(IProject project, boolean processOtherBuilders) {
+		if (project == null) {
+			return;
+		}
+		restartBuildImmediately.put(project, !processOtherBuilders);
+	}
+
+	/**
+	 * Hook for builders to request a rebuild for given projects. This request will
+	 * cause the main build loop to cycle once again <b>at least</b> for given
+	 * projects but the build loop also may run over all projects in build cycle if
+	 * the {@link #requestRebuild()} flag was set.
+	 * <p>
+	 * <b>Note</b> the current project (that is currently built with current
+	 * builder) will be not rebuilt in the current builld cycle, but scheduled for
+	 * rebuild on next round. To perform immediate rebuild of the current project,
+	 * use {@link #requestRebuild(IProject, boolean)}.
+	 *
+	 * @param toBeRebuilt to be rebuilt on next build round
+	 * @param current     project currently built with current builder
+	 */
+	void requestRebuild(Collection<IProject> toBeRebuilt, IProject current) {
+		for (IProject project : toBeRebuilt) {
+			if (project != null && hasBeenBuilt(project) || project.equals(current)) {
+				requestRebuildOnNextRound(project);
+			}
+		}
+	}
+
+	/**
+	 * Hook for builders to request an <b>unconditional<b> rebuild for given
+	 * project, in the next build round, independently if the project was already
+	 * built or not.
+	 */
+	void requestRebuildOnNextRound(IProject project) {
+		projectsToRebuild.add(project);
+	}
+
+	/**
 	 * Sets the builder infos for the given build config.  The builder infos are
 	 * an ArrayList of BuilderPersistentInfo.
 	 * The list includes entries for all builders that are
@@ -1353,4 +1510,25 @@
 			Policy.log(status);
 		return workspace.getRoot();
 	}
+
+	/**
+	 * @return {@code true} if the projects build loop can restart immediately after
+	 *         rebuild request, {@code false} if the loop will continue building all
+	 *         not yet built projects
+	 */
+	public boolean isEarlyExitFromBuildLoopAllowed() {
+		return earlyExitFromBuildLoopAllowed;
+	}
+
+	/**
+	 * @param earlyExitFromBuildLoopAllowed {@code true} if the projects build loop
+	 *                                      should restart immediately after rebuild
+	 *                                      request, {@code false} if the loop
+	 *                                      should continue building all not yet
+	 *                                      built projects
+	 */
+	public void setEarlyExitFromBuildLoopAllowed(boolean earlyExitFromBuildLoopAllowed) {
+		this.earlyExitFromBuildLoopAllowed = earlyExitFromBuildLoopAllowed;
+	}
+
 }
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
index 4a6ab86..0e9a9c6 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/InternalBuilder.java
@@ -14,6 +14,7 @@
  *******************************************************************************/
 package org.eclipse.core.internal.events;
 
+import java.util.Collection;
 import java.util.Map;
 import org.eclipse.core.internal.resources.ICoreConstants;
 import org.eclipse.core.internal.watson.ElementTree;
@@ -181,6 +182,20 @@
 		buildManager.requestRebuild();
 	}
 
+	/*
+	 * @see IncrementalProjectBuilder#requestProjectRebuild
+	 */
+	public void requestProjectRebuild(boolean processOtherBuilders) {
+		buildManager.requestRebuild(getProject(), processOtherBuilders);
+	}
+
+	/*
+	 * @see IncrementalProjectBuilder#requestProjectsRebuild
+	 */
+	public void requestProjectsRebuild(Collection<IProject> projects) {
+		buildManager.requestRebuild(projects, getProject());
+	}
+
 	final void setCallOnEmptyDelta(boolean value) {
 		this.callOnEmptyDelta = value;
 	}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
index 3ba80e4..74611b2 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
@@ -17,6 +17,7 @@
  *******************************************************************************/
 package org.eclipse.core.resources;
 
+import java.util.Collection;
 import java.util.Map;
 import org.eclipse.core.internal.events.InternalBuilder;
 import org.eclipse.core.runtime.*;
@@ -346,16 +347,28 @@
 	}
 
 	/**
-	 * Indicates that this builder made changes that affect a build configuration that
-	 * precedes this build configuration in the currently executing build order, and thus a
-	 * rebuild will be necessary.
+	 * Indicates that this builder made changes that affect a build configuration
+	 * that precedes this build configuration in the currently executing build
+	 * order, and thus a rebuild will be necessary.
 	 * <p>
-	 * This is an advanced feature that builders should use with caution. This
-	 * can cause workspace builds to iterate until no more builders require
-	 * rebuilds.
+	 * <b>Note:</b> this method will schedule rebuild for all projects involved in
+	 * the current build cycle!
+	 * <ul>
+	 * <li>If concrete projects that require rebuild are known, it is better to use
+	 * {@link #requestProjectsRebuild(Collection)} instead to avoid overhead.</li>
+	 * <li>If only one project should be rebuilt, it is better to use
+	 * {@link #requestProjectRebuild(boolean)} where additionally one could avoid
+	 * extra work by skipping not yet executed builders.</li>
+	 * </ul>
+	 * </p>
+	 * <p>
+	 * This is an advanced feature that builders should use with caution. This can
+	 * cause workspace builds to iterate until no more builders require rebuilds.
 	 * </p>
 	 *
 	 * @see #hasBeenBuilt(IProject)
+	 * @see #requestProjectsRebuild(Collection)
+	 * @see #requestProjectRebuild(boolean)
 	 * @since 2.1
 	 */
 	@Override
@@ -364,6 +377,64 @@
 	}
 
 	/**
+	 * Indicates that this builder generated or detected new input for currently
+	 * running project build, and thus a project rebuild will be necessary in the
+	 * current build round.
+	 * <p>
+	 * The builders configured to run after the current one will be still processed
+	 * if {@code processOtherBuilders} is set to {@code true}. To force an immediate
+	 * rebuild of a project {@code processOtherBuilders} argument should be set to
+	 * {@code false}.
+	 * </p>
+	 *
+	 * <b>Note</b> if {@code processOtherBuilders} is set to {@code false}, the
+	 * project that is built with current builder will be only rebuilt again, if
+	 * this builder is not the first one configured to run.
+	 * </p>
+	 * <p>
+	 * This is an advanced feature that builders should use with caution. This can
+	 * cause workspace builds to iterate until no more builders require rebuilds.
+	 * </p>
+	 *
+	 * @param processOtherBuilders to continue building project with other builders
+	 *                             and not start project build from beginning
+	 *                             immediately
+	 *
+	 * @see #needRebuild()
+	 * @see #requestProjectsRebuild(Collection)
+	 * @since 3.17
+	 */
+	@Override
+	public final void requestProjectRebuild(boolean processOtherBuilders) {
+		super.requestProjectRebuild(processOtherBuilders);
+	}
+
+	/**
+	 * Indicates that this builder generated or detected new input for given
+	 * projects and thus a rebuild for given projects will be necessary in the next
+	 * build round.
+	 * <p>
+	 * <b>Note</b> if the given collection contains the current project (that is
+	 * currently built with current builder), the current project will be not
+	 * rebuilt immediately, but scheduled for rebuild on next round. To perform
+	 * immediate rebuild of the current project, use
+	 * {@link #requestProjectRebuild(boolean)}.
+	 * </p>
+	 * <p>
+	 * This is an advanced feature that builders should use with caution. This can
+	 * cause workspace builds to iterate until no more builders require rebuilds.
+	 * </p>
+	 *
+	 * @see #needRebuild()
+	 * @see #requestProjectRebuild(boolean)
+	 * @since 3.17
+	 */
+	@Override
+	public final void requestProjectsRebuild(Collection<IProject> projects) {
+		super.requestProjectsRebuild(projects);
+	}
+
+	/**
 	 * Sets initialization data for this builder.
 	 * <p>
 	 * This method is part of the {@link IExecutableExtension} interface.
diff --git a/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF b/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF
index cb34554..8f6a761 100644
--- a/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Eclipse Core Tests Resources
 Bundle-SymbolicName: org.eclipse.core.tests.resources; singleton:=true
-Bundle-Version: 3.10.1700.qualifier
+Bundle-Version: 3.10.1800.qualifier
 Bundle-Vendor: Eclipse.org
 Export-Package: org.eclipse.core.tests.filesystem,
  org.eclipse.core.tests.internal.alias,
diff --git a/tests/org.eclipse.core.tests.resources/plugin.xml b/tests/org.eclipse.core.tests.resources/plugin.xml
index 386e23f..a65cbff 100644
--- a/tests/org.eclipse.core.tests.resources/plugin.xml
+++ b/tests/org.eclipse.core.tests.resources/plugin.xml
@@ -19,6 +19,14 @@
       <run class="org.eclipse.core.tests.internal.builders.SortBuilder"/> 
     </builder>
   </extension>
+  <extension point="org.eclipse.core.resources.builders" id="rebuildingbuilder" name="Rebuilding Builder">
+    <builder
+          callOnEmptyDelta="true">
+      <run
+            class="org.eclipse.core.tests.internal.builders.RebuildingBuilder">
+      </run> 
+    </builder>
+  </extension>
   <extension point="org.eclipse.core.resources.builders" id="brokenbuilder" name="Broken Builder">
     <builder>
       <run class="org.eclipse.core.tests.internal.builders.BrokenBuilder"/> 
diff --git a/tests/org.eclipse.core.tests.resources/pom.xml b/tests/org.eclipse.core.tests.resources/pom.xml
index 8c442ee..0cf3da4 100644
--- a/tests/org.eclipse.core.tests.resources/pom.xml
+++ b/tests/org.eclipse.core.tests.resources/pom.xml
@@ -18,7 +18,7 @@
     <version>4.24.0-SNAPSHOT</version>
   </parent>
   <artifactId>org.eclipse.core.tests.resources</artifactId>
-  <version>3.10.1700-SNAPSHOT</version>
+  <version>3.10.1800-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 
   <properties>
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/AllTests.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/AllTests.java
index 55a0e62..00dabdc 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/AllTests.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/AllTests.java
@@ -19,6 +19,7 @@
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses({ BuilderCycleTest.class, BuilderEventTest.class, BuilderNatureTest.class, BuilderTest.class,
+		RebuildTest.class,
 		BuildDeltaVerificationTest.class, CustomBuildTriggerTest.class, EmptyDeltaTest.class,
 		MultiProjectBuildTest.class, RelaxedSchedRuleBuilderTest.class, BuildConfigurationsTest.class,
 		BuildContextTest.class, ParallelBuildChainTest.class, ComputeProjectOrderTest.class })
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildTest.java
new file mode 100644
index 0000000..1188dc9
--- /dev/null
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildTest.java
@@ -0,0 +1,1759 @@
+/*******************************************************************************
+ *  Copyright (c) 2000, 2017 IBM Corporation and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Alexander Kurtakov <akurtako@redhat.com> - Bug 459343
+ *******************************************************************************/
+package org.eclipse.core.tests.internal.builders;
+
+import java.util.List;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.resources.*;
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * This class tests builder behavior related to re-building
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class RebuildTest extends AbstractBuilderTest {
+
+	final String builderName = RebuildingBuilder.BUILDER_NAME;
+	private final int maxBuildIterations;
+
+	public RebuildTest(String name) {
+		super(name);
+		maxBuildIterations = getWorkspace().getDescription().getMaxBuildIterations();
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		getWorkspace().getRoot().delete(true, null);
+		// Turn auto-building off
+		setAutoBuilding(false);
+		boolean earlyExitAllowed = ((Workspace) getWorkspace()).getBuildManager()
+				.isEarlyExitFromBuildLoopAllowed();
+		assertFalse("early exit shouldn't be set", earlyExitAllowed);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		getWorkspace().getRoot().delete(true, null);
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(maxBuildIterations);
+		getWorkspace().setDescription(description);
+		RebuildingBuilder.getInstances().clear();
+		allowEarlyBuildLoopExit(false);
+		super.tearDown();
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with a single project
+	 */
+	public void testSingleProjectPropagationAndNoOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project = getWorkspace().getRoot().getProject(getName());
+
+		// Create and open a project
+		project.create(getMonitor());
+		project.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+		project.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Confirm basic functionality first - one build per builder
+		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild - it will cause main build to loop two times
+		// and project loop will run as usually (no repetitions)
+		b1.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second builder requests rebuild - it will cause main build to loop two times
+		// and project loop will restart from the first one, so first and second
+		// builder will run 3 times
+		b2.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Third builder requests rebuild - it will cause main build to loop two times
+		// and project loop will restart from the first one, so all builders will run 3
+		// times
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second and third builder request rebuild - it will cause main build to loop
+		// two times
+		// and project loop will restart from the first one
+		// first and second builder 4 times, third 3 times
+		b2.setRequestProjectRebuild(project, 1);
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(4, b1.buildsCount());
+		assertEquals(4, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// Third builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		int expectedRebuildsFirstBuilders = max * max;
+		int expectedRebuildslastBuilder = max;
+		b2.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with a single project
+	 */
+	public void testSingleProjectPropagationAndOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project = getWorkspace().getRoot().getProject(getName());
+
+		// Create and open a project
+		project.create(getMonitor());
+		project.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"), createCommand(desc, builderName, "builder3"), });
+		project.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Confirm basic functionality first - one build per builder
+		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild - it will cause main build to loop two times
+		// and project loop will run once more
+		b1.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second builder requests rebuild - it will cause main build to loop two times
+		// and project loop will run once more
+		b2.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Third builder requests rebuild - it will cause main build to loop two times
+		// and project loop will restart from the first one, so all builders will run 3
+		// times
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second and third builder request rebuild - it will cause main build to loop
+		// two times and project loop will restart once more
+		b2.setRequestProjectRebuild(project, 1);
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// Third builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		int expectedRebuildsFirstBuilders = max * max;
+		int expectedRebuildslastBuilder = max * max;
+		b2.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with a single project
+	 */
+	public void testSingleProjectNoPropagationAndProcessOtherBuilder() throws Exception {
+		// Create some resource handles
+		IProject project = getWorkspace().getRoot().getProject(getName());
+
+		// Create and open a project
+		project.create(getMonitor());
+		project.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+		project.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Confirm basic functionality first - one build per builder
+		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild - one repetition
+		b1.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second builder requests rebuild - two repetitions for all
+		b2.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Third builder requests rebuild - all builders will run twice
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second and third builder request rebuild - all builders will run twice
+		// times, last one one time less
+		b2.setRequestProjectRebuild(project, 1);
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// Second builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		int expectedRebuildsFirstBuilders = max;
+		int expectedRebuildslastBuilder = max;
+		b2.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Third builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		expectedRebuildslastBuilder = max;
+		b3.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with a single project
+	 */
+	public void testSingleProjectNoPropagationNoOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project = getWorkspace().getRoot().getProject(getName());
+
+		// Create and open a project
+		project.create(getMonitor());
+		project.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+		project.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Confirm basic functionality first - one build per builder
+		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild - no repetitions
+		b1.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second builder requests rebuild - first & second builder will run twice
+		b2.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Third builder requests rebuild - all builders will run three twice
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second and third builder request rebuild - 1 & 2 builders will run three
+		// times, last one one time less
+		b2.setRequestProjectRebuild(project, 1);
+		b3.setRequestProjectRebuild(project, 1);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// Second builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		int expectedRebuildsFirstBuilders = max;
+		int expectedRebuildslastBuilder = 1;
+		b2.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Third builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		expectedRebuildslastBuilder = max;
+		b3.setRequestProjectRebuild(project, rebuilds);
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstBuilders, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstBuilders, b2.buildsCount());
+		assertEquals(expectedRebuildslastBuilder, b3.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsPropagationAndNoOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+				project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"),
+				createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild - it will cause main build to
+		// loop two times for both projects
+		b1.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in one project request rebuild - it will cause main build
+		// to loop only one extra time
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(4, b1.buildsCount());
+		assertEquals(4, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// One builder in each project requests rebuild - it will cause main build
+		// to loop only one extra time
+		b1.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in all projects request rebuild - it will cause main build
+		// to loop only one extra time
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(4, b1.buildsCount());
+		assertEquals(4, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(4, b4.buildsCount());
+		assertEquals(4, b5.buildsCount());
+		assertEquals(3, b6.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsPropagationAndProcessOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"), createCommand(desc, builderName, "builder3"), });
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"), createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild - it will cause main build to
+		// loop two times for both projects and one extra time for current one
+		b1.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All builders in one project request rebuild - it will cause main build to
+		// loop two times for both projects and one extra time for current one
+		// to loop only one extra time
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// One builder in each project requests rebuild - it will cause main build
+		// to loop only one extra time and every project twice
+		b1.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(3, b4.buildsCount());
+		assertEquals(3, b5.buildsCount());
+		assertEquals(3, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All builders in all projects request rebuild - it will cause main build
+		// to loop only one extra time and every project twice
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(3, b4.buildsCount());
+		assertEquals(3, b5.buildsCount());
+		assertEquals(3, b6.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 * and early exit enabled
+	 */
+	public void testMultipleProjectsPropagationAndNoOtherBuildersWithEarlyExit() throws Exception {
+		// turn rebuild request propagation to other projects in same loop off
+		allowEarlyBuildLoopExit(true);
+
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+				project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"),
+				createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second and third builder request rebuild - it will cause main build to loop
+		// one extra time for the first project but the second project will be built
+		// only once (on second loop cycle)
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(4, b1.buildsCount());
+		assertEquals(4, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// One builder in each project requests rebuild - it will cause main build
+		// to loop two times more
+		b1.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(3, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in all projects request rebuild - it will cause main build
+		// to loop three times (only two extra builds for the second project)
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(5, b1.buildsCount());
+		assertEquals(5, b2.buildsCount());
+		assertEquals(4, b3.buildsCount());
+
+		assertEquals(4, b4.buildsCount());
+		assertEquals(4, b5.buildsCount());
+		assertEquals(3, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// Third builder requests N rebuilds - it will cause main build to loop
+		// not more than (max * max) times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		int expectedRebuildsFirstProject = max * max;
+		int expectedBuildCycles = max;
+		b2.setRequestProjectRebuild(project1, rebuilds);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(expectedRebuildsFirstProject, b1.buildsCount());
+		assertEquals(expectedRebuildsFirstProject, b2.buildsCount());
+		assertEquals(expectedBuildCycles, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+	}
+
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsNoPropagationNoOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"),
+				createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b1.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b2.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in one project request rebuild - still no extra round for
+		// second project
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// One builder in each project requests rebuild - still no extra round for
+		// the main loop
+		b2.setRequestProjectRebuild(project1, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in all projects request rebuild - still no extra round for
+		// the main loop
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(3, b4.buildsCount());
+		assertEquals(3, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsNoPropagationAndOtherBuilders() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"), createCommand(desc, builderName, "builder3"), });
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"), createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b1.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b2.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All builders in one project request rebuild - still no extra round for
+		// second project
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// One builder in each project requests rebuild - still no extra round for
+		// the main loop
+		b2.setRequestProjectRebuild(project1, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All builders in all projects request rebuild - still no extra round for
+		// the main loop
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsNoPropagationNoOtherBuildersEarlyExit() throws Exception {
+		// turn rebuild request propagation to other projects in same loop off
+		allowEarlyBuildLoopExit(true);
+
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder1"),
+				createCommand(desc, builderName, "builder2"),
+				createCommand(desc, builderName, "builder3"),
+				});
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+		RebuildingBuilder b2 = builders.get(1);
+		RebuildingBuilder b3 = builders.get(2);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] {
+				createCommand(desc, builderName, "builder3"),
+				createCommand(desc, builderName, "builder5"),
+				createCommand(desc, builderName, "builder6"), });
+		project2.setDescription(desc, getMonitor());
+
+		builders.forEach(b -> b.setPropagateRebuild(false));
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(6, builders.size());
+		RebuildingBuilder b4 = builders.get(3);
+		RebuildingBuilder b5 = builders.get(4);
+		RebuildingBuilder b6 = builders.get(5);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b1.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second builder requests rebuild - it will not cause main build to
+		// loop extra time
+		b2.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in one project request rebuild - still no extra round for
+		// second project
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(1, b4.buildsCount());
+		assertEquals(1, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// One builder in each project requests rebuild - still no extra round for
+		// the main loop
+		b2.setRequestProjectRebuild(project1, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		assertEquals(2, b4.buildsCount());
+		assertEquals(2, b5.buildsCount());
+		assertEquals(1, b6.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(false));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders in all projects request rebuild - still no extra round for
+		// the main loop
+		b1.setRequestProjectRebuild(project1, 1);
+		b2.setRequestProjectRebuild(project1, 1);
+		b3.setRequestProjectRebuild(project1, 1);
+		b4.setRequestProjectRebuild(project2, 1);
+		b5.setRequestProjectRebuild(project2, 1);
+		b6.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(3, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+
+		assertEquals(3, b4.buildsCount());
+		assertEquals(3, b5.buildsCount());
+		assertEquals(2, b6.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsPropagationAndNoOtherBuildersExplicitRebuild() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+		IProject project3 = getWorkspace().getRoot().getProject(getName() + 3);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+		project3.create(getMonitor());
+		project3.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder1"), });
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder2"), });
+		project2.setDescription(desc, getMonitor());
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(2, builders.size());
+		RebuildingBuilder b2 = builders.get(1);
+
+		// Create and set a build spec for the project
+		desc = project3.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder3"), });
+		project3.setDescription(desc, getMonitor());
+
+		project3.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(3, builders.size());
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild for second project - it not rebuild anything
+		// because second is not built yet
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild for second project, second for third
+		// same here: no rebuilds
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// First builder requests rebuild for every project.
+		// Only first one will be rebuilt, others aren't because they weren't built
+		// before
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project1, 1);
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Second builder requests rebuild for p1
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All projects request rebuilds for next projects: only p1
+		// will be rebuilt
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project3, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// Third builder requests rebuild for all: all will be rebuilt
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project2, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// All builders requests rebuild for all: all will be rebuilt
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project1, project2, project3);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project1, project2, project3);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, project2, project3);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		// One builder requests rebuild for same project and sets the global flag
+		// all will be rebuilt once
+		b1.setPropagateRebuild(true);
+		b1.setRequestProjectRebuild(project1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(false));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// First and third builder requests N rebuilds - it will cause main build to
+		// loop not more than max times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project3, rebuilds);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, rebuilds);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(max - 1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(max - 2, b3.buildsCount());
+	}
+
+	/**
+	 * Tests IncrementProjectBuilder.requestProjectRebuild with multiple projects
+	 */
+	public void testMultipleProjectsPropagationAndProcessOtherBuildersExplicitRebuild() throws Exception {
+		// Create some resource handles
+		IProject project1 = getWorkspace().getRoot().getProject(getName() + 1);
+		IProject project2 = getWorkspace().getRoot().getProject(getName() + 2);
+		IProject project3 = getWorkspace().getRoot().getProject(getName() + 3);
+
+		// Create and open a project
+		project1.create(getMonitor());
+		project1.open(getMonitor());
+		project2.create(getMonitor());
+		project2.open(getMonitor());
+		project3.create(getMonitor());
+		project3.open(getMonitor());
+
+		// Create and set a build spec for the project
+		IProjectDescription desc = project1.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder1"), });
+		project1.setDescription(desc, getMonitor());
+
+		// do an initial build to create builders
+		project1.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		List<RebuildingBuilder> builders = RebuildingBuilder.getInstances();
+		assertEquals(desc.getBuildSpec().length, builders.size());
+		RebuildingBuilder b1 = builders.get(0);
+
+		// Create and set a build spec for the project
+		desc = project2.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder2"), });
+		project2.setDescription(desc, getMonitor());
+
+		project2.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(2, builders.size());
+		RebuildingBuilder b2 = builders.get(1);
+
+		// Create and set a build spec for the project
+		desc = project3.getDescription();
+
+		desc.setBuildSpec(new ICommand[] { createCommand(desc, builderName, "builder3"), });
+		project3.setDescription(desc, getMonitor());
+
+		project3.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor());
+		assertEquals(3, builders.size());
+		RebuildingBuilder b3 = builders.get(2);
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Confirm basic functionality first - one build per builder
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild for second project - it not rebuild anything
+		// because second is not built yet
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild for second project, second for third
+		// same here: no rebuilds
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// First builder requests rebuild for every project.
+		// Only first one will be rebuilt, others aren't because they weren't built
+		// before
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project1, 1);
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Second builder requests rebuild for p1
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All projects request rebuilds for next projects: only p1
+		// will be rebuilt
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project2, 1);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project3, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(1, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// Third builder requests rebuild for all: all will be rebuilt
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project2, 1);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project3, 1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// All builders requests rebuild for all: all will be rebuilt
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project1, project2, project3);
+		b2.setPropagateRebuild(false);
+		b2.setRequestProjectRebuild(project1, project2, project3);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, project2, project3);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(2, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		// One builder requests rebuild for same project and sets the global flag
+		// one will be built twice, others once
+		b1.setPropagateRebuild(true);
+		b1.setRequestProjectRebuild(project1);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(3, b1.buildsCount());
+		assertEquals(2, b2.buildsCount());
+		assertEquals(2, b3.buildsCount());
+		builders.forEach(RebuildingBuilder::reset);
+		builders.forEach(b -> b.setPropagateRebuild(true));
+		builders.forEach(b -> b.setProcessOtherBuilders(true));
+
+		final int max = 5;
+		IWorkspaceDescription description = getWorkspace().getDescription();
+		description.setMaxBuildIterations(max);
+		getWorkspace().setDescription(description);
+
+		// First and third builder requests N rebuilds - it will cause main build to
+		// loop not more than max times (both inner / outer loop use limit)
+		int rebuilds = 42;
+		b1.setPropagateRebuild(false);
+		b1.setRequestProjectRebuild(project3, rebuilds);
+		b3.setPropagateRebuild(false);
+		b3.setRequestProjectRebuild(project1, rebuilds);
+
+		getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		assertEquals(max - 1, b1.buildsCount());
+		assertEquals(1, b2.buildsCount());
+		assertEquals(max - 2, b3.buildsCount());
+	}
+
+	private void allowEarlyBuildLoopExit(boolean earlyExitFromInnerBuildLoopAllowed) {
+		((Workspace) getWorkspace()).getBuildManager()
+				.setEarlyExitFromBuildLoopAllowed(earlyExitFromInnerBuildLoopAllowed);
+	}
+
+}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildingBuilder.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildingBuilder.java
new file mode 100644
index 0000000..7ff3fef
--- /dev/null
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RebuildingBuilder.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Andrey Loskutov and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *    Andrey Loskutov - initial API and implementation
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.builders;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Builder that can request rebuilds
+ */
+public class RebuildingBuilder extends TestBuilder {
+	public static final String BUILDER_NAME = "org.eclipse.core.tests.resources.rebuildingbuilder";
+
+	static List<RebuildingBuilder> instances = new ArrayList<>();
+
+	public static List<RebuildingBuilder> getInstances() {
+		return instances;
+	}
+
+	private boolean propagateRebuild;
+	private boolean processOtherBuilders;
+
+	Map<IProject, Integer> projectToRebuild = new HashMap<>();
+
+	List<IProject> projectsBuilt = new ArrayList<>();
+
+	BuilderRuleCallback callback = new BuilderRuleCallback() {
+		@Override
+		public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
+			projectsBuilt.add(getProject());
+			if (projectToRebuild.size() > 1 || projectToRebuild.get(getProject()) == null) {
+				Set<IProject> toRebuild = new HashSet<>();
+				Set<IProject> toRemove = new HashSet<>();
+				Set<Entry<IProject, Integer>> entries = projectToRebuild.entrySet();
+				for (Entry<IProject, Integer> entry : entries) {
+					IProject project = entry.getKey();
+					Integer rebuild = entry.getValue();
+					if (rebuild.intValue() > 0) {
+						toRebuild.add(project);
+						int count = rebuild - 1;
+						if (count > 0) {
+							setRequestProjectRebuild(project, count);
+						} else {
+							toRemove.add(project);
+						}
+					}
+				}
+				toRemove.forEach(p -> projectToRebuild.remove(p));
+				requestProjectsRebuild(toRebuild);
+			} else {
+				IProject project = getProject();
+				Integer rebuild = projectToRebuild.get(project);
+				if (rebuild != null && rebuild.intValue() > 0) {
+					requestProjectRebuild(isProcessOtherBuilders());
+					if (isPropagateRebuild()) {
+						needRebuild();
+					}
+					int count = rebuild - 1;
+					if (count > 0) {
+						setRequestProjectRebuild(project, count);
+					} else {
+						projectToRebuild.remove(project);
+					}
+				}
+			}
+			return super.build(kind, args, monitor);
+		}
+	};
+
+	public RebuildingBuilder() {
+		instances.add(this);
+		setRuleCallback(callback);
+		setPropagateRebuild(true);
+		setProcessOtherBuilders(true);
+	}
+
+	public void setRequestProjectRebuild(IProject p, Integer count) {
+		projectToRebuild.put(p, count);
+	}
+
+	public void setRequestProjectRebuild(IProject... projects) {
+		for (IProject project : projects) {
+			projectToRebuild.put(project, 1);
+		}
+	}
+
+	public int buildsCount() {
+		return projectsBuilt.size();
+	}
+
+	@Override
+	public void reset() {
+		super.reset();
+		projectsBuilt.clear();
+		projectToRebuild.clear();
+		setRuleCallback(callback);
+		setPropagateRebuild(true);
+		setProcessOtherBuilders(true);
+	}
+
+	public boolean isPropagateRebuild() {
+		return propagateRebuild;
+	}
+
+	public void setPropagateRebuild(boolean propagateRebuild) {
+		this.propagateRebuild = propagateRebuild;
+	}
+
+	public boolean isProcessOtherBuilders() {
+		return processOtherBuilders;
+	}
+
+	public void setProcessOtherBuilders(boolean processOtherBuilders) {
+		this.processOtherBuilders = processOtherBuilders;
+	}
+}