### Eclipse Workspace Patch 1.0 #P org.eclipse.core.resources Index: src/org/eclipse/core/internal/events/BuildManager.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.resources/src/org/eclipse/core/internal/events/BuildManager.java,v retrieving revision 1.125 diff -u -r1.125 BuildManager.java --- src/org/eclipse/core/internal/events/BuildManager.java 1 Feb 2011 11:37:52 -0000 1.125 +++ src/org/eclipse/core/internal/events/BuildManager.java 20 Apr 2011 22:03:25 -0000 @@ -1083,55 +1083,73 @@ } /** - * Returns the scheduling rule that is required for building the project. + * Discover the scheduling rule required to build the passed in build configuration array + * @param buildConfigs Array of build configurations to build + * @param trigger + * @param builderName name of the builder, or null for all build commands + * @param args arguments to the IProject.build command, or null + * @return the scheduling rule that is required for building the passed in build configurations. */ - public ISchedulingRule getRule(IBuildConfiguration buildConfiguration, int trigger, String builderName, Map args) { - IProject project = buildConfiguration.getProject(); + public ISchedulingRule getRule(IBuildConfiguration[] buildConfigs, int trigger, String builderName, Map args) { + ISchedulingRule wrRule = workspace.getRoot(); MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null); - if (builderName == null) { - final ICommand[] commands; - if (project.isAccessible()) { - Set rules = new HashSet(); - commands = ((Project) project).internalGetDescription().getBuildSpec(false); - boolean hasNullBuildRule = false; - BuildContext context = new BuildContext(buildConfiguration); - for (int i = 0; i < commands.length; i++) { - BuildCommand command = (BuildCommand) commands[i]; - try { - IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context); - if (builder != null) { - ISchedulingRule builderRule = builder.getRule(trigger, args); - if (builderRule != null) - rules.add(builderRule); - else - hasNullBuildRule = true; + Set rules = new HashSet(); + boolean hasNullBuildRule = false; + + // Loop over all the build configurations collecting together the build rules. + for (IBuildConfiguration buildConfiguration : buildConfigs) { + IProject project = buildConfiguration.getProject(); + if (builderName == null) { + final ICommand[] commands; + if (project.isAccessible()) { + commands = ((Project) project).internalGetDescription().getBuildSpec(false); + BuildContext context = new BuildContext(buildConfiguration); + for (int i = 0; i < commands.length; i++) { + BuildCommand command = (BuildCommand) commands[i]; + try { + IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, i, status, context); + if (builder != null) { + ISchedulingRule builderRule = builder.getRule(trigger, args); + if (builderRule != null) + rules.add(builderRule); + else + hasNullBuildRule = true; + } + } catch (CoreException e) { + status.add(e.getStatus()); } - } catch (CoreException e) { - status.add(e.getStatus()); } + // Can short-circuit if there's a null rule && a non-null rule + if (!rules.isEmpty() && hasNullBuildRule) + break; + } + } else { + // Returns the derived resources for the specified builderName + ICommand command = getCommand(project, builderName, args); + try { + IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, -1, status); + if (builder != null) { + ISchedulingRule rule = builder.getRule(trigger, args); + if (wrRule.equals(rule)) + return wrRule; + rules.add(rule); + } + } catch (CoreException e) { + status.add(e.getStatus()); } - if (rules.isEmpty()) - return null; - // Bug 306824 - Builders returning a null rule can't work safely if other builders require a non-null rule - // Be pessimistic and fall back to the default build rule (workspace root) in this case. - if (!hasNullBuildRule) - return new MultiRule(rules.toArray(new ISchedulingRule[rules.size()])); - } - } else { - // Returns the derived resources for the specified builderName - ICommand command = getCommand(project, builderName, args); - try { - IncrementalProjectBuilder builder = getBuilder(buildConfiguration, command, -1, status); - if (builder != null) - return builder.getRule(trigger, args); - - } catch (CoreException e) { - status.add(e.getStatus()); } } // Log any errors if (!status.isOK()) Policy.log(status); - return workspace.getRoot(); + + // If no rules required, return null + if (rules.isEmpty()) + return null; + // Bug 306824 - Builders returning a null rule can't work safely if other builders require a non-null rule + // Be pessimistic and fall back to the default build rule (workspace root) in this case. + if (!hasNullBuildRule) + return new MultiRule(rules.toArray(new ISchedulingRule[rules.size()])); + return wrRule; } } Index: src/org/eclipse/core/internal/resources/Project.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java,v retrieving revision 1.182 diff -u -r1.182 Project.java --- src/org/eclipse/core/internal/resources/Project.java 24 Feb 2011 12:12:30 -0000 1.182 +++ src/org/eclipse/core/internal/resources/Project.java 20 Apr 2011 22:03:25 -0000 @@ -609,7 +609,7 @@ } finally { workspace.endOperation(rule, false, innerMonitor); } - final ISchedulingRule buildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args); + final ISchedulingRule buildRule = workspace.getBuildManager().getRule(new IBuildConfiguration[]{config}, trigger, builderName, args); try { IStatus result; workspace.prepareOperation(buildRule, innerMonitor); Index: src/org/eclipse/core/internal/resources/Workspace.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java,v retrieving revision 1.244 diff -u -r1.244 Workspace.java --- src/org/eclipse/core/internal/resources/Workspace.java 1 Feb 2011 11:37:52 -0000 1.244 +++ src/org/eclipse/core/internal/resources/Workspace.java 20 Apr 2011 22:03:26 -0000 @@ -410,8 +410,8 @@ recursivelyAddBuildConfigs(configs, referenced[i]); } } catch (CoreException e) { - // Not possible, we've checked that the project + configuration are accessible. - Assert.isTrue(false); + // This shouldn't be possible, but currently is possible, as computing the references occurs + // outside of an operation... See Bug 343256 for how this should be fixed. } } @@ -440,62 +440,74 @@ */ private void buildInternal(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { monitor = Policy.monitorFor(monitor); - final ISchedulingRule rule = getRuleFactory().buildRule(); + final ISchedulingRule wrRule = getRuleFactory().buildRule(); try { monitor.beginTask("", Policy.opWork); //$NON-NLS-1$ try { - prepareOperation(rule, monitor); + prepareOperation(wrRule, monitor); beginOperation(true); aboutToBuild(this, trigger); - IStatus result; - try { - - // Calculate the build-order having called the pre-build notification (which may change build order) - // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. - IBuildConfiguration[] requestedConfigs = configs; - if (configs == EMPTY_BUILD_CONFIG_ARRAY) { - if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) - configs = getBuildOrder(); - else { - // clean all accessible configurations - List configArr = new ArrayList(); - IProject[] prjs = getRoot().getProjects(); - for (int i = 0; i < prjs.length; i++) - if (prjs[i].isAccessible()) - configArr.addAll(Arrays.asList(prjs[i].getBuildConfigs())); - configs = configArr.toArray(new IBuildConfiguration[configArr.size()]); - } - } else { - // Order the passed in build configurations + resolve references if requested - Set refsList = new HashSet(); - for (int i = 0 ; i < configs.length ; i++) { - // Check project + build configuration are accessible. - if (!configs[i].getProject().isAccessible() || !configs[i].getProject().hasBuildConfig(configs[i].getName())) - continue; - refsList.add(configs[i]); - // Find transitive closure of referenced project buildConfigs - if (buildReferences) - recursivelyAddBuildConfigs(refsList, configs[i]); - } + } finally { + endOperation(wrRule, false, monitor); + } + IStatus result; + ISchedulingRule buildRule = null; + try { - // Order the referenced project buildConfigs - ProjectBuildConfigOrder order = computeProjectBuildConfigOrder(refsList.toArray(new IBuildConfiguration[refsList.size()])); - configs = order.buildConfigurations; + // Calculate the build-order having called the pre-build notification (which may change build order) + // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. + IBuildConfiguration[] requestedConfigs = configs; + if (configs == EMPTY_BUILD_CONFIG_ARRAY) { + if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) + configs = getBuildOrder(); + else { + // clean all accessible configurations + List configArr = new ArrayList(); + IProject[] prjs = getRoot().getProjects(); + for (int i = 0; i < prjs.length; i++) + if (prjs[i].isAccessible()) + configArr.addAll(Arrays.asList(prjs[i].getBuildConfigs())); + configs = configArr.toArray(new IBuildConfiguration[configArr.size()]); + } + } else { + // Order the passed in build configurations + resolve references if requested + Set refsList = new HashSet(); + for (int i = 0 ; i < configs.length ; i++) { + // Check project + build configuration are accessible. + if (!configs[i].getProject().isAccessible() || !configs[i].getProject().hasBuildConfig(configs[i].getName())) + continue; + refsList.add(configs[i]); + // Find transitive closure of referenced project buildConfigs + if (buildReferences) + recursivelyAddBuildConfigs(refsList, configs[i]); } - result = getBuildManager().build(configs, requestedConfigs, trigger, Policy.subMonitorFor(monitor, Policy.opWork)); - } finally { - //must fire POST_BUILD if PRE_BUILD has occurred - broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); + // Order the referenced project buildConfigs + ProjectBuildConfigOrder order = computeProjectBuildConfigOrder(refsList.toArray(new IBuildConfiguration[refsList.size()])); + configs = order.buildConfigurations; } - if (!result.isOK()) - throw new ResourceException(result); + buildRule = getBuildManager().getRule(configs, trigger, null, null); + prepareOperation(buildRule, monitor); + //don't open the tree eagerly because it will be wasted if no build occurs + beginOperation(false); + result = getBuildManager().build(configs, requestedConfigs, trigger, Policy.subMonitorFor(monitor, Policy.opWork)); } finally { - //building may close the tree, but we are still inside an operation so open it - if (tree.isImmutable()) - newWorkingTree(); - endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + //must fire POST_BUILD if PRE_BUILD has occurred + endOperation(buildRule, false, monitor); + try { + prepareOperation(wrRule, monitor); + //don't open the tree eagerly because it will be wasted if no change occurs + beginOperation(false); + broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); + //building may close the tree, so open it + if (getElementTree().isImmutable()) + newWorkingTree(); + } finally { + endOperation(wrRule, false, Policy.subMonitorFor(monitor, Policy.endOpWork)); + } } + if (!result.isOK()) + throw new ResourceException(result); } finally { monitor.done(); } #P org.eclipse.core.tests.resources Index: src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java,v retrieving revision 1.2 diff -u -r1.2 RelaxedSchedRuleBuilderTest.java --- src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java 1 Feb 2011 11:37:48 -0000 1.2 +++ src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java 20 Apr 2011 22:03:26 -0000 @@ -10,13 +10,12 @@ *******************************************************************************/ package org.eclipse.core.tests.internal.builders; -import java.util.Map; +import java.util.*; import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; -import org.eclipse.core.runtime.jobs.ISchedulingRule; -import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.*; import org.eclipse.core.tests.harness.TestBarrier; import org.eclipse.core.tests.internal.builders.TestBuilder.BuilderRuleCallback; @@ -205,4 +204,164 @@ tb2.waitForStatus(TestBarrier.STATUS_DONE); } + private HashSet getRulesAsSet(ISchedulingRule rule) { + HashSet rules = new HashSet(); + if (rule == null) + return rules; + if (rule instanceof MultiRule) + rules.addAll(Arrays.asList(((MultiRule) rule).getChildren())); + else + rules.add(rule); + return rules; + } + + /** + * Tests for regression in running the build with reduced scheduling rules. + * @throws Exception + */ + public void testBug343256() throws Exception { + String name = "testBug343256"; + setAutoBuilding(false); + final IProject project = getWorkspace().getRoot().getProject(name); + create(project, false); + + IProjectDescription desc = project.getDescription(); + desc.setBuildSpec(new ICommand[] {createCommand(desc, EmptyDeltaBuilder.BUILDER_NAME, "Project1Build1"), createCommand(desc, EmptyDeltaBuilder2.BUILDER_NAME, "Project1Build2")}); + project.setDescription(desc, getMonitor()); + + // Ensure the builder is instantiated + project.build(IncrementalProjectBuilder.CLEAN_BUILD, getMonitor()); + + final TestBarrier tb1 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); + final TestBarrier tb2 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); + + // Scheduling rules to returng from #getRule + final ISchedulingRule[] getRules = new ISchedulingRule[2]; + final ISchedulingRule[] buildRules = new ISchedulingRule[2]; + + // Create a builder set a null scheduling rule + EmptyDeltaBuilder builder = EmptyDeltaBuilder.getInstance(); + EmptyDeltaBuilder2 builder2 = EmptyDeltaBuilder2.getInstance(); + + // Set the rule call-back + builder.setRuleCallback(new BuilderRuleCallback() { + public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map args) { + tb1.waitForStatus(TestBarrier.STATUS_START); + return getRules[0]; + } + + public IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { + HashSet h1 = getRulesAsSet(Job.getJobManager().currentRule()); + HashSet h2 = getRulesAsSet(buildRules[0]); + // shared scheduling rule + assertTrue(h1.equals(h2)); + tb1.setStatus(TestBarrier.STATUS_DONE); + return super.build(kind, args, monitor); + } + }); + // Set the rule call-back + builder2.setRuleCallback(new BuilderRuleCallback() { + public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map args) { + tb2.waitForStatus(TestBarrier.STATUS_START); + return getRules[1]; + } + + public IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { + HashSet h1 = getRulesAsSet(Job.getJobManager().currentRule()); + HashSet h2 = getRulesAsSet(buildRules[1]); + assertTrue(h1.equals(h2)); + tb2.setStatus(TestBarrier.STATUS_DONE); + return super.build(kind, args, monitor); + } + }); + + // IProject.build() + Job j = new Job("IProject.build()") { + protected IStatus run(IProgressMonitor monitor) { + try { + project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } catch (CoreException e) { + fail(e.toString()); + } + return Status.OK_STATUS; + } + }; + invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); + + // IWorkspace.build() + j = new Job("IWorkspace.build()") { + protected IStatus run(IProgressMonitor monitor) { + try { + getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } catch (CoreException e) { + fail(e.toString()); + } + return Status.OK_STATUS; + } + }; + invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); + + // IWorkspace.build(IBuildConfiguration[],...) + j = new Job("IWorkspace.build(IBuildConfiguration[],...)") { + protected IStatus run(IProgressMonitor monitor) { + try { + getWorkspace().build(new IBuildConfiguration[] {project.getActiveBuildConfig()}, IncrementalProjectBuilder.FULL_BUILD, true, monitor); + } catch (CoreException e) { + fail(e.toString()); + } + return Status.OK_STATUS; + } + }; + invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); + + } + + /** + * Helper method do invoke a set of tests on Bug343256 using the different sets of builder API + */ + private void invokeTestBug343256(IProject project, ISchedulingRule[] getRules, ISchedulingRule[] buildRules, TestBarrier tb1, TestBarrier tb2, Job j) { + // Test 1 - IProject#build project sched rule + getRules[0] = getRules[1] = project; + buildRules[0] = buildRules[1] = new MultiRule(new ISchedulingRule[] {getRules[0]}); + tb1.setStatus(TestBarrier.STATUS_START); + tb2.setStatus(TestBarrier.STATUS_START); + j.schedule(); + tb1.waitForStatus(TestBarrier.STATUS_DONE); + tb2.waitForStatus(TestBarrier.STATUS_DONE); + + // Test 2 - IProject#build null rule + getRules[0] = getRules[1] = null; + buildRules[0] = buildRules[1] = null; + tb1.setStatus(TestBarrier.STATUS_START); + tb2.setStatus(TestBarrier.STATUS_START); + j.schedule(); + tb1.waitForStatus(TestBarrier.STATUS_DONE); + tb2.waitForStatus(TestBarrier.STATUS_DONE); + + // Test 3 - IProject#build mixed projects + getRules[0] = project; + getRules[1] = getWorkspace().getRoot().getProject("other"); + buildRules[0] = buildRules[1] = new MultiRule(new ISchedulingRule[] {getRules[0], getRules[1]}); + // TODO it should be: + // buildRules[0] = getRules[0] + // buildRules[1] = getRules[1] + tb1.setStatus(TestBarrier.STATUS_START); + tb2.setStatus(TestBarrier.STATUS_START); + j.schedule(); + tb1.waitForStatus(TestBarrier.STATUS_DONE); + tb2.waitForStatus(TestBarrier.STATUS_DONE); + + // Test 4 - IProject#build project + null + getRules[0] = project; + getRules[1] = null; + buildRules[0] = buildRules[1] = getWorkspace().getRoot(); + // TODO it should be: + // buildRules[0] = getRules[0] + // buildRules[1] = null + tb1.setStatus(TestBarrier.STATUS_START); + tb2.setStatus(TestBarrier.STATUS_START); + j.schedule(); + tb1.waitForStatus(TestBarrier.STATUS_DONE); + tb2.waitForStatus(TestBarrier.STATUS_DONE); + } }