Index: src/main/groovy/org/gradle/api/internal/AbstractTask.java
===================================================================
--- src/main/groovy/org/gradle/api/internal/AbstractTask.java	(revision 1620)
+++ src/main/groovy/org/gradle/api/internal/AbstractTask.java	(working copy)
@@ -17,15 +17,15 @@
 
 import groovy.util.AntBuilder;
 import groovy.lang.MissingPropertyException;
+import groovy.lang.Closure;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
 import org.gradle.api.*;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.tasks.OptimizationContextImpl;
 import org.gradle.api.internal.plugins.DefaultConvention;
-import org.gradle.api.tasks.StopActionException;
-import org.gradle.api.tasks.StopExecutionException;
-import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.*;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.DefaultStandardOutputCapture;
 import org.gradle.api.logging.LogLevel;
@@ -38,7 +38,7 @@
 /**
  * @author Hans Dockter
  */
-public abstract class AbstractTask implements TaskInternal {
+public abstract class AbstractTask implements TaskInternal {                                               
     private static Logger logger = LoggerFactory.getLogger(AbstractTask.class);
     private static Logger buildLogger = LoggerFactory.getLogger(Task.class);
 
@@ -54,6 +54,9 @@
 
     private boolean executed;
 
+   // will be set to a default value of true before task is executed.  Derived classes may set to a more useful value
+    private boolean didWork = false;
+
     private boolean enabled = true;
 
     private String path = null;
@@ -66,6 +69,10 @@
 
     private String description;
 
+    OptimizationContextImpl opt;
+
+    private OptimizationAction optimizationAction;
+
     protected AbstractTask() {
         dynamicObjectHelper = new DynamicObjectHelper(this);
         dynamicObjectHelper.setConvention(new DefaultConvention());
@@ -79,6 +86,7 @@
         path = project.absolutePath(name);
         dynamicObjectHelper = new DynamicObjectHelper(this);
         dynamicObjectHelper.setConvention(new DefaultConvention());
+        opt = new OptimizationContextImpl(this);
     }
 
     public AntBuilder getAnt() {
@@ -129,6 +137,14 @@
         dependencies.setValues(dependsOn);
     }
 
+    public void onlyIf(Closure closure) {
+        optimizationAction = convertClosureToOptAction(closure);
+    }
+
+    public void onlyIf(OptimizationAction optimizationAction) {
+        this.optimizationAction = optimizationAction;
+    }
+
     public boolean isExecuted() {
         return executed;
     }
@@ -141,6 +157,18 @@
         this.executed = executed;
     }
 
+    public boolean isDidWork() {
+        return didWork;
+    }
+
+    public boolean getDidWork() {
+        return isDidWork();
+    }
+
+    public void setDidWork(boolean didWork) {
+        this.didWork = didWork;
+    }
+
     public boolean isEnabled() {
         return enabled;
     }
@@ -166,24 +194,31 @@
         executing = true;
         logger.debug("Starting to execute Task: {}", path);
         if (!isSkipped()) {
-            logger.info(Logging.LIFECYCLE, "{}", path);
-            standardOutputCapture.start();
-            for (TaskAction action : actions) {
-                logger.debug("Executing Action:");
-                try {
-                    doExecute(action);
-                } catch (StopExecutionException e) {
-                    logger.info("Execution stopped by some action with message: {}", e.getMessage());
-                    break;
-                } catch (StopActionException e) {
-                    logger.debug("Action stopped by some action with message: {}", e.getMessage());
+            if (optimizationAction == null ||
+                    project.getBuild().getStartParameter().isNoOpt() ||
+                    optimizationAction.execute(opt)) {
+                logger.info(Logging.LIFECYCLE, "{}", path);
+                didWork = true;   // assume true unless changed during execution
+                standardOutputCapture.start();
+                for (TaskAction action : actions) {
+                    logger.debug("Executing Action:");
+                    try {
+                        doExecute(action);
+                    } catch (StopExecutionException e) {
+                        logger.info("Execution stopped by some action with message: {}", e.getMessage());
+                        break;
+                    } catch (StopActionException e) {
+                        logger.debug("Action stopped by some action with message: {}", e.getMessage());
                     } catch (Throwable t) {
-                    executing = false;
-                    standardOutputCapture.stop();
-                    throw new GradleScriptException(String.format("Execution failed for %s.", this), t, project.getBuildScriptSource());
+                        executing = false;
+                        standardOutputCapture.stop();
+                        throw new GradleScriptException(String.format("Execution failed for %s.", this), t, project.getBuildScriptSource());
+                    }
                 }
+                standardOutputCapture.stop();
+            } else {
+                logger.info(Logging.LIFECYCLE, "{} SKIPPED for optimization", path);
             }
-            standardOutputCapture.stop();
         }
         executing = false;
         executed = true;
@@ -338,4 +373,24 @@
     public void setDescription(String description) {
         this.description = description;
     }
+
+    protected OptimizationAction convertClosureToOptAction(final Closure closure) {
+        return new OptimizationAction() {
+            public boolean execute(OptimizationContext opt) {
+                boolean result = true;
+                closure.setDelegate(opt);
+                Object obj = closure.call(new Object[]{opt.getTask()});
+                if (obj != null) {
+                    if (obj instanceof Boolean) {
+                        result = (Boolean)obj;
+                    }
+                }
+                return result;
+            }
+        };
+    }
+
+    protected OptimizationContext getOptimizationContext() {
+        return opt;
+    }
 }
Index: src/main/groovy/org/gradle/api/internal/tasks/OptimizationContextImpl.java
===================================================================
--- src/main/groovy/org/gradle/api/internal/tasks/OptimizationContextImpl.java	(revision 0)
+++ src/main/groovy/org/gradle/api/internal/tasks/OptimizationContextImpl.java	(revision 0)
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.OptimizationContext;
+import org.gradle.api.tasks.TaskDependency;
+
+public class OptimizationContextImpl implements OptimizationContext
+{
+    private Task task;
+
+    public OptimizationContextImpl(Task task) {
+        this.task = task;
+    }
+
+
+    /**
+     * Access the Task that is being optimized
+     * @return the Task
+     */
+    public Task getTask() {
+        return task;
+    }
+
+    /**
+    * <p>Checks if any of the tasks that this task depends on {@link org.gradle.api.Task#getDidWork() didWork}.</p>
+    * @return true if any task this task depends on did work.
+    */
+    public boolean dependsOnTaskDidWork() {
+        TaskDependency dependency = task.getTaskDependencies();
+        for (Task depTask : dependency.getDependencies(task)) {
+            if (depTask.getDidWork()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: src/main/groovy/org/gradle/api/Task.java
===================================================================
--- src/main/groovy/org/gradle/api/Task.java	(revision 1620)
+++ src/main/groovy/org/gradle/api/Task.java	(working copy)
@@ -23,6 +23,7 @@
 import org.gradle.api.tasks.StopActionException;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.OptimizationAction;
 import org.slf4j.Logger;
 
 import java.util.List;
@@ -194,6 +195,48 @@
     Task dependsOn(Object... paths);
 
     /**
+     * <p>Execute the task only if the closure returns true.  The closure will be evaluated at the
+     * task execution time, not during configuration.  The closure will be passed a single parameter,
+     * this task.  The closure will delegate to an {@link org.gradle.api.tasks.OptimizationContext OptimizationContext}.
+     * </p>
+     * <p>If the closure returns false, the task will not execute, but will log a message of
+     * 'SKIPPED for optimization'.</p>
+     * <p>Typical usage:</p>
+     * <code>myTask.onlyIf{ dependsOnTaskDidWork() }
+     * </code>
+     * @see #getDidWork()
+     * @param closure code to execute to determine if task should be run
+     */
+    void onlyIf(Closure closure);
+
+    /**
+     * <p>Execute the task only if the OptimizationAction returns true.
+     * The OptimizationAction will be executed at task execution time, not during configuration.
+     * The OptimizationAction may access the task through {@link org.gradle.api.tasks.OptimizationContext#getTask()}.
+     * </p>
+     * <p>If the OptimizationAction returns false, the task will not execute, but will log a message of
+     * 'SKIPPED for optimization'.</p>
+     * <p>Typical usage (from Java):</p>
+     * <pre>myTask.onlyIf(new OptimizationAction() {
+     *    boolean execute(OptimizationContext opt) {
+     *       return opt.dependsOnTaskDidWork();
+     *    }
+     * });
+     * </pre>
+     * @see #getDidWork()
+     * @param optimizationAction code to execute to determine if task should be run
+     */
+    void onlyIf(OptimizationAction optimizationAction);
+
+    /**
+     * <p>Checks if the task actually did any work.  Even if a Task executes, it may determine that it has nothing
+     * to do.  For example, the Compile task may determine that source files have not changed since the last
+     * time a the task was run.</p>
+     * @return true if this task did any work
+     */
+    boolean getDidWork();
+
+    /**
      * <p>Returns true if this task has been executed.</p>
      *
      * @return true if this task has been executed already, false otherwise.
Index: src/main/groovy/org/gradle/api/tasks/compile/AntGroovyc.groovy
===================================================================
--- src/main/groovy/org/gradle/api/tasks/compile/AntGroovyc.groovy	(revision 1620)
+++ src/main/groovy/org/gradle/api/tasks/compile/AntGroovyc.groovy	(working copy)
@@ -20,6 +20,8 @@
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.gradle.util.BootstrapUtil
+import org.gradle.api.tasks.util.AntTaskAccess
+import org.codehaus.groovy.ant.Groovyc
 
 /**
  * Please not: includeAntRuntime=false is ignored if groovyc is used in non fork mode. In this case the runtime classpath is
@@ -31,15 +33,29 @@
 class AntGroovyc {
     private static Logger logger = LoggerFactory.getLogger(AntGroovyc)
 
+    private final AntTaskAccess listener = new AntTaskAccess() { task->
+        if (task instanceof Groovyc) {
+            numFilesCompiled = task.fileList.length;
+        }
+    }
+    int numFilesCompiled;
+
     List nonGroovycJavacOptions = ['verbose', 'deprecation', 'includeJavaRuntime', 'includeAntRuntime', 'optimize', 'fork', 'failonerror', 'listfiles', 'nowarn', 'depend']
 
     public void execute(antNode, List sourceDirs, List groovyIncludes, List groovyExcludes, List groovyJavaIncludes,
                         List groovyJavaExcludes, File targetDir, List classpath, String sourceCompatibility,
                         String targetCompatibility, GroovyCompileOptions groovyOptions, CompileOptions compileOptions, List taskClasspath) {
 
-        String groovyc = """taskdef(name: 'groovyc', classname: 'org.codehaus.groovy.ant.Groovyc')
-    mkdir(dir: '${GradleUtil.unbackslash(targetDir.absolutePath)}')
-    groovyc(
+        String groovyc = """int numFilesCompiled = 0
+    org.gradle.api.tasks.util.AntTaskAccess listener = new org.gradle.api.tasks.util.AntTaskAccess() { task->
+        if (task != null && task instanceof org.codehaus.groovy.ant.Groovyc) {
+            numFilesCompiled = task.fileList.length;
+        }
+    }
+    ant.project.addBuildListener(listener)
+    ant.taskdef(name: 'groovyc', classname: 'org.codehaus.groovy.ant.Groovyc')
+    ant.mkdir(dir: '${GradleUtil.unbackslash(targetDir.absolutePath)}')
+    ant.groovyc(
         [includeAntRuntime: false,
         srcdir: '${sourceDirs.collect {GradleUtil.unbackslash(it)}.join(':')}',
         destdir: '${GradleUtil.unbackslash(targetDir)}',
@@ -52,8 +68,10 @@
             ${groovyJavaExcludes.collect {'exclude(name: \'' + it + '\')'}.join('\n')}
         }
     }
+    ant.project.removeBuildListener(listener)
+    return numFilesCompiled
 """
-        GradleUtil.executeIsolatedAntScript(taskClasspath, groovyc)
+        numFilesCompiled = (Integer)GradleUtil.executeIsolatedAntScript(taskClasspath, groovyc)
     }
 
     private Map filterNonGroovycOptions(CompileOptions options) {
Index: src/main/groovy/org/gradle/api/tasks/compile/AntJavac.groovy
===================================================================
--- src/main/groovy/org/gradle/api/tasks/compile/AntJavac.groovy	(revision 1620)
+++ src/main/groovy/org/gradle/api/tasks/compile/AntJavac.groovy	(working copy)
@@ -18,15 +18,24 @@
 
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import org.apache.tools.ant.BuildListener
+import org.gradle.api.tasks.util.AntTaskAccess
+import org.apache.tools.ant.taskdefs.Javac
 
 /**
  * @author Hans Dockter
  */
 class AntJavac {
     private static Logger logger = LoggerFactory.getLogger(AntJavac)
-
     static final String CLASSPATH_ID = 'compile.classpath'
 
+    private final AntTaskAccess listener = new AntTaskAccess() { task->
+        if (task instanceof Javac) {
+            numFilesCompiled = task.fileList.length;
+        }
+    }
+    int numFilesCompiled;
+
     void execute(List sourceDirs, List includes, List excludes, File targetDir, Iterable classpath, String sourceCompatibility,
                  String targetCompatibility, CompileOptions compileOptions, AntBuilder ant) {
         createAntClassPath(ant, classpath)
@@ -39,6 +48,7 @@
                 source: sourceCompatibility
         ]
         targetDir.mkdirs()
+        ant.project.addBuildListener(listener)
         ant.javac(otherArgs + compileOptions.optionMap()) {
             includes.each {
                 include(name: it)
@@ -52,6 +62,7 @@
                 }
             }
         }
+        ant.project.removeBuildListener(listener)
     }
 
     private void createAntClassPath(AntBuilder ant, Iterable classpath) {
Index: src/main/groovy/org/gradle/api/tasks/compile/Compile.java
===================================================================
--- src/main/groovy/org/gradle/api/tasks/compile/Compile.java	(revision 1620)
+++ src/main/groovy/org/gradle/api/tasks/compile/Compile.java	(working copy)
@@ -100,6 +100,7 @@
 
         antCompile.execute(existingSourceDirs, includes, excludes, getDestinationDir(), getClasspath(),
                 getSourceCompatibility(), getTargetCompatibility(), options, getProject().getAnt());
+        setDidWork(antCompile.getNumFilesCompiled() > 0);
     }
 
     public Iterable<File> getClasspath() {
Index: src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
===================================================================
--- src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java	(revision 1620)
+++ src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java	(working copy)
@@ -73,6 +73,7 @@
             classpath = GUtil.addLists(getClasspath());
             antCompile.execute(existingSourceDirs, getIncludes(), getExcludes(), getDestinationDir(), classpath,
                     getSourceCompatibility(), getTargetCompatibility(), getOptions(), getProject().getAnt());
+            setDidWork(antCompile.getNumFilesCompiled() > 0);            
         }
         List existingGroovySourceDirs = existentDirsFilter.findExistingDirs(getGroovySourceDirs());
         if (existingGroovySourceDirs.size() > 0) {
@@ -85,6 +86,7 @@
             antGroovyCompile.execute(getProject().getAnt(), existingGroovySourceDirs, getGroovyIncludes(), getGroovyExcludes(),
                     getGroovyJavaIncludes(), getGroovyExcludes(), getDestinationDir(), classpath, getSourceCompatibility(),
                     getTargetCompatibility(), getGroovyOptions(), getOptions(), taskClasspath);
+            setDidWork(antGroovyCompile.getNumFilesCompiled() > 0);            
         }
     }
 
Index: src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
===================================================================
--- src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy	(revision 1620)
+++ src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy	(working copy)
@@ -24,8 +24,8 @@
 class AntGroovydoc {
     void execute(List sourceDirs, File destDir, AntBuilder ant, List taskClasspath) {
         String groovydoc = """
-    taskdef(name: 'groovydoc', classname: 'org.codehaus.groovy.ant.Groovydoc')
-    groovydoc(
+    ant.taskdef(name: 'groovydoc', classname: 'org.codehaus.groovy.ant.Groovydoc')
+    ant.groovydoc(
        destdir: '${GradleUtil.unbackslash(destDir)}',
        sourcepath: '${sourceDirs.collect {GradleUtil.unbackslash(it)}.join(':')}')
 """
Index: src/main/groovy/org/gradle/api/tasks/OptimizationAction.java
===================================================================
--- src/main/groovy/org/gradle/api/tasks/OptimizationAction.java	(revision 0)
+++ src/main/groovy/org/gradle/api/tasks/OptimizationAction.java	(revision 0)
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+/**
+ * Interface used to provide test criteria for {@link org.gradle.api.Task#onlyIf(OptimizationAction)}.
+ * @author Steve Appling
+ */
+public interface OptimizationAction {
+
+    /**
+     * Method should return true if the task being optimzed should execute.
+     * The provided OptimizationContext has helper methods for optimization
+     * and can provide access to the task.
+     * @param opt
+     * @return true if task should execute
+     */
+    boolean execute(OptimizationContext opt);
+}
Index: src/main/groovy/org/gradle/api/tasks/OptimizationContext.java
===================================================================
--- src/main/groovy/org/gradle/api/tasks/OptimizationContext.java	(revision 0)
+++ src/main/groovy/org/gradle/api/tasks/OptimizationContext.java	(revision 0)
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks;
+
+import org.gradle.api.Task;
+
+/**
+ * OptimizationContext contains helper methods for optimizing
+ * {@link org.gradle.api.Task Task} execution.  This class is the delegate for
+ * evaluation of the {@link org.gradle.api.Task#onlyIf(groovy.lang.Closure) Task.onlyIf}
+ * closure.
+ * @author Steve Appling
+ */
+public interface OptimizationContext
+{
+    /**
+     * Access the Task that is being optimized
+     * @return the Task
+     */
+    public Task getTask();
+
+   /**
+    * <p>Checks if any of the tasks that this task depends on {@link org.gradle.api.Task#getDidWork() didWork}.</p>
+    * @return true if any task this task depends on did work.
+    */
+   public boolean dependsOnTaskDidWork();
+}
Index: src/main/groovy/org/gradle/api/tasks/util/AntTaskAccess.java
===================================================================
--- src/main/groovy/org/gradle/api/tasks/util/AntTaskAccess.java	(revision 0)
+++ src/main/groovy/org/gradle/api/tasks/util/AntTaskAccess.java	(revision 0)
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.util;
+
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.Task;
+import groovy.lang.Closure;
+
+/**
+ * <p>Provides a mechanism to get access to an Ant Task object after
+ * the task has been executed.  Use ant.project.addBuildListener to
+ * add an AntTaskAccess object as a build listener.
+ * </p>
+ * @author Steve Appling
+ */
+public class AntTaskAccess implements BuildListener {
+    Closure closure;
+
+    /**
+     * Default CTOR.  If this version is used, you must call {@link #setClosure(groovy.lang.Closure)}
+     * before using this object.
+     */
+    public AntTaskAccess() {}
+
+    /**
+     * CTOR for normal use.  The provided closure will be called after
+     * each task has finished.  The closure will be passed a single parameter,
+     * the Task object of the task that just finished executing.
+     * @param closure
+     */
+    public AntTaskAccess(Closure closure) {
+        this.closure = closure;
+    }
+
+    public void setClosure(Closure closure) {
+        this.closure = closure;
+    }
+
+    // implementation of BuildListener
+    public void buildStarted(BuildEvent buildEvent) {
+    }
+
+    public void buildFinished(BuildEvent buildEvent) {
+    }
+
+    public void targetStarted(BuildEvent buildEvent) {
+    }
+
+    public void targetFinished(BuildEvent buildEvent) {
+    }
+
+    public void taskStarted(BuildEvent buildEvent) {
+    }
+
+    public void taskFinished(BuildEvent buildEvent) {
+        closure.call(new Object[] {buildEvent.getTask()});
+    }
+
+    public void messageLogged(BuildEvent buildEvent) {
+    }
+}
Index: src/main/groovy/org/gradle/DefaultCommandLine2StartParameterConverter.java
===================================================================
--- src/main/groovy/org/gradle/DefaultCommandLine2StartParameterConverter.java	(revision 1620)
+++ src/main/groovy/org/gradle/DefaultCommandLine2StartParameterConverter.java	(working copy)
@@ -63,6 +63,7 @@
     private static final String VERSION = "v";
     private static final String CACHE = "C";
     private static final String DRY_RUN = "m";
+    private static final String NO_OPT = "o";
     private static final String HELP = "h";
 
     OptionParser parser = new OptionParser() {
@@ -93,6 +94,7 @@
             acceptsAll(WrapUtil.toList(EMBEDDED_SCRIPT, "embedded"), "Specify an embedded build script.").withRequiredArg().ofType(String.class);
             acceptsAll(WrapUtil.toList(PROJECT_DEPENDENCY_TASK_NAMES, "dep-tasks"), "Specify additional tasks for building project dependencies.").withRequiredArg().ofType(String.class);
             acceptsAll(WrapUtil.toList(NO_PROJECT_DEPENDENCY_REBUILD, "no-rebuild"), "Do not rebuild project dependencies.");
+            acceptsAll(WrapUtil.toList(NO_OPT, "no-opt"), "Ignore any task optimization.");
             acceptsAll(WrapUtil.toList(HELP, "?", "help"), "Shows this help message");
         }
     };
@@ -225,6 +227,10 @@
             startParameter.setDryRun(true);
         }
 
+        if (options.has(NO_OPT)) {
+            startParameter.setNoOpt(true);
+        }
+
         startParameter.setLogLevel(getLogLevel(options));
         return startParameter;
     }
Index: src/main/groovy/org/gradle/StartParameter.java
===================================================================
--- src/main/groovy/org/gradle/StartParameter.java	(revision 1620)
+++ src/main/groovy/org/gradle/StartParameter.java	(working copy)
@@ -74,6 +74,7 @@
     private boolean showHelp = false;
     private boolean showVersion = false;
     private boolean dryRun = false;
+    private boolean noOpt = false;
 
     /**
      * Creates a {@code StartParameter} with default values. This is roughly equivalent to running Gradle on the
@@ -111,6 +112,7 @@
         startParameter.showHelp = showHelp;
         startParameter.showVersion = showVersion;
         startParameter.dryRun = dryRun;
+        startParameter.noOpt = noOpt;
         return startParameter;
     }
 
@@ -383,6 +385,14 @@
         this.dryRun = dryRun;
     }
 
+    public boolean isNoOpt() {
+        return noOpt;
+    }
+
+    public void setNoOpt(boolean noOpt) {
+        this.noOpt = noOpt;
+    }
+
     /**
      * Sets the settings file to use for the build. Use null to use the default settings file.
      *
Index: src/main/groovy/org/gradle/util/GradleUtil.groovy
===================================================================
--- src/main/groovy/org/gradle/util/GradleUtil.groovy	(revision 1620)
+++ src/main/groovy/org/gradle/util/GradleUtil.groovy	(working copy)
@@ -65,9 +65,7 @@
 AntBuilder ant = loader.loadClass('groovy.util.AntBuilder').newInstance()
 ant.project.removeBuildListener(ant.project.getBuildListeners()[0])
 ant.project.addBuildListener(loader.loadClass("org.gradle.logging.AntLoggingAdapter").newInstance())
-ant.sequential {
-    $filling
-}
+$filling
 """
     }
 
@@ -76,7 +74,7 @@
         antBuilder.project.addBuildListener(buildListener)
     }
 
-    static executeIsolatedAntScript(List loaderClasspath, String filling) {
+    static Object executeIsolatedAntScript(List loaderClasspath, String filling) {
         ClassLoader oldCtx = Thread.currentThread().contextClassLoader
         File toolsJar = ClasspathUtil.getToolsJar()
         logger.debug("Tools jar is: {}", toolsJar)
@@ -89,10 +87,13 @@
         }
         ClassLoader newLoader = new URLClassLoader(taskUrlClasspath, oldCtx.parent)
         Thread.currentThread().contextClassLoader = newLoader
-        String scriptText = createIsolatedAntScript(filling)
-        logger.debug("Using groovyc as: {}", scriptText)
-        newLoader.loadClass("groovy.lang.GroovyShell").newInstance(newLoader).evaluate(
+        try {
+            String scriptText = createIsolatedAntScript(filling)
+            logger.debug("Using groovyc as: {}", scriptText)
+            return newLoader.loadClass("groovy.lang.GroovyShell").newInstance(newLoader).evaluate(
                 scriptText)
-        Thread.currentThread().contextClassLoader = oldCtx
+        } finally {
+            Thread.currentThread().contextClassLoader = oldCtx
+        }
     }
 }
Index: src/samples/java/optimization/build.gradle
===================================================================
--- src/samples/java/optimization/build.gradle	(revision 0)
+++ src/samples/java/optimization/build.gradle	(revision 0)
@@ -0,0 +1,33 @@
+usePlugin 'java'
+
+sourceCompatibility = 1.5
+version = '1.0'
+manifest.mainAttributes(
+    'Implementation-Title': 'Gradle Quickstart',
+    'Implementation-Version': version
+)
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    testCompile group: 'junit', name: 'junit', version: '4.+'
+}
+
+compile.doLast { println "compile didWork = ${compile.didWork}" }
+compileTests.doLast { println "compileTests didWork = ${compile.didWork}" }
+
+test {
+    options.systemProperties['property'] = 'value'
+    onlyIf {
+        dependsOnTaskDidWork() || compile.didWork
+    }
+}
+
+uploadArchives {
+    repositories {
+       flatDir(dirs: file('repos'))
+    }
+}
Index: src/samples/java/optimization/src/main/java/org/gradle/Person.java
===================================================================
--- src/samples/java/optimization/src/main/java/org/gradle/Person.java	(revision 0)
+++ src/samples/java/optimization/src/main/java/org/gradle/Person.java	(revision 0)
@@ -0,0 +1,16 @@
+package org.gradle;
+
+import org.apache.commons.collections.list.GrowthList;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+        new GrowthList();
+    }
+
+    public String getName() {
+        return name;
+    }
+}
Index: src/samples/java/optimization/src/test/java/org/gradle/PersonTest.java
===================================================================
--- src/samples/java/optimization/src/test/java/org/gradle/PersonTest.java	(revision 0)
+++ src/samples/java/optimization/src/test/java/org/gradle/PersonTest.java	(revision 0)
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
Index: src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
===================================================================
--- src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy	(revision 1620)
+++ src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy	(working copy)
@@ -16,94 +16,100 @@
 
 package org.gradle.api.internal
 
-//import org.gradle.api.tasks.AbstractTaskTest
+import org.gradle.api.tasks.AbstractTaskTest
+import org.gradle.api.DefaultTask
+import org.gradle.util.WrapUtil
+import static org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+
 /**
  * @author Hans Dockter
  */
-//class DefaultTaskTest extends AbstractTaskTest {
-//    DefaultTask defaultTask
-//
-//    Object testCustomPropValue;
-//
-//    @Before public void setUp()  {
-//        super.setUp()
-//        testCustomPropValue = new Object()
-//        defaultTask = new DefaultTask(project, AbstractTaskTest.TEST_TASK_NAME)
-//    }
-//
-//    AbstractTask getTask() {
-//        defaultTask
-//    }
-//
-//    @Test public void testDefaultTask() {
-//        assertEquals new TreeSet(), defaultTask.dependsOn
-//        assertEquals([], defaultTask.actions)
-//    }
-//
-//    @Test public void testHasUsefulToString() {
-//        assertEquals('task \':taskname\'', task.toString())
-//    }
-//
-//    @Test public void testDoFirstWithClosureDelegatesToProject() {
-//        Closure testAction = {}
-//        defaultTask.doFirst(testAction)
-//        assertSame(getProject(), testAction.delegate)
-//        assertEquals(Closure.OWNER_FIRST, testAction.getResolveStrategy())
-//    }
-//
-//    @Test public void testDoFirstWithClosure() {
-//        List<Integer> executed = new ArrayList<Integer>();
-//        Closure testAction1 = { executed.add(1) }
-//        Closure testAction2 = { -> executed.add(2) }
-//        Closure testAction3 = { task -> executed.add(3) }
-//        defaultTask.doFirst(testAction1)
-//        defaultTask.doFirst(testAction2)
-//        defaultTask.doFirst(testAction3)
-//        defaultTask.execute()
-//        assertEquals(executed, WrapUtil.toList(3, 2, 1))
-//    }
-//
-//    @Test
-//    void getAdditonalProperties() {
-//        defaultTask.additionalProperties.customProp = testCustomPropValue
-//        assertSame(testCustomPropValue, defaultTask."customProp")
-//    }
-//
-//    @Test
-//    void setAdditonalProperties() {
-//        defaultTask."customProp" = testCustomPropValue
-//        assertSame(testCustomPropValue, defaultTask.additionalProperties.customProp)
-//    }
-//
-//    @Test
-//    void getAndSetConventionProperties() {
-//        TestConvention convention = new TestConvention()
-//        defaultTask.convention.plugins.test = convention
-//        assertTrue(defaultTask.hasProperty('conventionProperty'))
-//        defaultTask.conventionProperty = 'value'
-//        assertEquals(defaultTask.conventionProperty, 'value')
-//        assertEquals(convention.conventionProperty, 'value')
-//    }
-//
-//    @Test
-//    void canCallConventionMethods() {
-//        defaultTask.convention.plugins.test = new TestConvention()
-//        assertEquals(defaultTask.conventionMethod('a', 'b').toString(), "a.b")
-//    }
-//
-//    @Test
-//    void getProperty() {
-//        defaultTask.additionalProperties.customProp = testCustomPropValue
-//        assertSame(testCustomPropValue, defaultTask.property("customProp"))
-//        assertSame(AbstractTaskTest.TEST_TASK_NAME, defaultTask.property("name"))
-//    }
-//
-//    @Test(expected = MissingPropertyException)
-//    void accessNonExistingProperty() {
-//        defaultTask."unknownProp"
-//    }
-//}
+class DefaultTaskTest extends AbstractTaskTest {
+    DefaultTask defaultTask
 
+    Object testCustomPropValue;
+
+    @Before public void setUp()  {
+        super.setUp()
+        testCustomPropValue = new Object()
+        defaultTask = new DefaultTask(project, AbstractTaskTest.TEST_TASK_NAME)
+    }
+
+    AbstractTask getTask() {
+        defaultTask
+    }
+
+    @Test public void testDefaultTask() {
+        assertEquals new TreeSet(), defaultTask.dependsOn
+        assertEquals([], defaultTask.actions)
+    }
+
+    @Test public void testHasUsefulToString() {
+        assertEquals('task \':taskname\'', task.toString())
+    }
+
+    @Test public void testDoFirstWithClosureDelegatesToProject() {
+        Closure testAction = {}
+        defaultTask.doFirst(testAction)
+        assertSame(getProject(), testAction.delegate)
+        assertEquals(Closure.OWNER_FIRST, testAction.getResolveStrategy())
+    }
+
+    @Test public void testDoFirstWithClosure() {
+        List<Integer> executed = new ArrayList<Integer>();
+        Closure testAction1 = { executed.add(1) }
+        Closure testAction2 = { -> executed.add(2) }
+        Closure testAction3 = { task -> executed.add(3) }
+        defaultTask.doFirst(testAction1)
+        defaultTask.doFirst(testAction2)
+        defaultTask.doFirst(testAction3)
+        defaultTask.execute()
+        assertEquals(executed, WrapUtil.toList(3, 2, 1))
+    }
+
+    @Test
+    void getAdditonalProperties() {
+        defaultTask.additionalProperties.customProp = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask."customProp")
+    }
+
+    @Test
+    void setAdditonalProperties() {
+        defaultTask."customProp" = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask.additionalProperties.customProp)
+    }
+
+    @Test
+    void getAndSetConventionProperties() {
+        TestConvention convention = new TestConvention()
+        defaultTask.convention.plugins.test = convention
+        assertTrue(defaultTask.hasProperty('conventionProperty'))
+        defaultTask.conventionProperty = 'value'
+        assertEquals(defaultTask.conventionProperty, 'value')
+        assertEquals(convention.conventionProperty, 'value')
+    }
+
+    @Test
+    void canCallConventionMethods() {
+        defaultTask.convention.plugins.test = new TestConvention()
+        assertEquals(defaultTask.conventionMethod('a', 'b').toString(), "a.b")
+    }
+
+    @Test
+    void getProperty() {
+        defaultTask.additionalProperties.customProp = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask.property("customProp"))
+        assertSame(AbstractTaskTest.TEST_TASK_NAME, defaultTask.property("name"))
+    }
+
+    @Test(expected = MissingPropertyException)
+    void accessNonExistingProperty() {
+        defaultTask."unknownProp"
+    }
+}
+
 class TestConvention {
     def conventionProperty
 
Index: src/test/groovy/org/gradle/api/internal/tasks/OptimizationContextImplTest.groovy
===================================================================
--- src/test/groovy/org/gradle/api/internal/tasks/OptimizationContextImplTest.groovy	(revision 0)
+++ src/test/groovy/org/gradle/api/internal/tasks/OptimizationContextImplTest.groovy	(revision 0)
@@ -0,0 +1,58 @@
+package org.gradle.api.internal.tasks;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.HelperUtil;
+
+import java.util.Set;
+import java.util.HashSet
+import org.gradle.util.JUnit4GroovyMockery;
+
+public class OptimizationContextImplTest {
+    private JUnit4GroovyMockery context;
+    private OptimizationContextImpl oc;
+    private Task taskMock;
+
+    public OptimizationContextImplTest() {
+        context = new JUnit4GroovyMockery()
+    }
+
+    @Before
+    public void setup() {
+        taskMock = context.mock(Task.class);
+        oc = new OptimizationContextImpl(taskMock);
+    }
+
+    @Test
+    def void testDependentTaskDidWork() {
+        AbstractProject project = HelperUtil.createRootProject();
+
+        final Set<Task> depTasks = new HashSet<Task>();
+        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
+
+        context.checking {
+            allowing(taskMock).getTaskDependencies(); will(returnValue(dependencyMock));
+            allowing(dependencyMock).getDependencies(taskMock); will(returnValue(depTasks));
+        }
+
+        DefaultTask task1 = new DefaultTask(project, "task1");
+        DefaultTask task2 = new DefaultTask(project, "task2");
+
+        depTasks.add(task1);
+        depTasks.add(task2);
+        assertFalse(oc.dependsOnTaskDidWork());
+
+        task2.setDidWork(true);
+        assertTrue(oc.dependsOnTaskDidWork());
+    }
+
+}
Index: src/test/groovy/org/gradle/api/tasks/AbstractTaskGroovyTest.groovy
===================================================================
--- src/test/groovy/org/gradle/api/tasks/AbstractTaskGroovyTest.groovy	(revision 0)
+++ src/test/groovy/org/gradle/api/tasks/AbstractTaskGroovyTest.groovy	(revision 0)
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.junit.Before
+import org.gradle.api.internal.AbstractTask
+import org.gradle.api.DefaultTask
+import org.gradle.api.internal.project.AbstractProject
+import org.gradle.api.Task
+import org.gradle.api.Project
+import static org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * This class is used to exercise some parts of AbstractTask that
+ * are easiest to test from Groovy.  Since AbstractTask is, well,
+ * abstract, this is actually using an instance of DefaultTask
+ * to exercise some of the methods in AbstractTask
+ * @author Steve Appling
+ */
+public class AbstractTaskGroovyTest extends AbstractTaskTest {
+    //private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    AbstractTask defaultTask
+
+
+    @Before
+    public void setUp() {
+        super.setUp()
+        defaultTask = new DefaultTask(project, AbstractTaskTest.TEST_TASK_NAME)
+    }
+
+    public AbstractTask getTask() {
+        return defaultTask
+    }
+
+    @Test
+    public void testOnlyIfClosureReturnTypes() {
+        AbstractTask task = getTask()
+        assertTrue(task.convertClosureToOptAction({ true}).execute(task.optimizationContext) )
+        assertTrue(task.convertClosureToOptAction({ Boolean.TRUE }).execute(task.optimizationContext) )
+
+        assertFalse(task.convertClosureToOptAction({ false}).execute(task.optimizationContext) )
+        assertFalse(task.convertClosureToOptAction({ Boolean.FALSE}).execute(task.optimizationContext) )
+
+        assertTrue(task.convertClosureToOptAction({ 'anything else'}).execute(task.optimizationContext) )
+        assertTrue(task.convertClosureToOptAction({ 'false'}).execute(task.optimizationContext) )
+    }
+
+    @Test
+    public void testOnlyIfClosureSetup() {
+        AbstractTask task = getTask()
+        task.onlyIf {taskParam ->
+            assertEquals(task, taskParam)
+            assertEquals(delegate, task.optimizationContext)
+        }
+        task.execute()
+    }
+
+    @Test
+    public void testExecutionStoppedByOptimizationWithClosure() {
+        AbstractTask task = getTask()
+        task.deleteAllActions()
+        task.doLast { fail 'Optimization failed' }
+        task.onlyIf { false }
+        task.execute()
+    }
+
+    @Test
+    public void testExecutionEnabledByOptimizationWithClosure() {
+        AbstractTask task = getTask()
+        task.deleteAllActions()
+        def worked = false
+        task.doLast { worked = true }
+        task.onlyIf { true }
+        task.execute()
+        assertTrue(worked)
+    }
+
+    @Test
+    public void testExecutionEnabledByDefault() {
+        AbstractTask task = getTask()
+        task.deleteAllActions()
+        def worked = false
+        task.doLast { worked = true }
+        // no onlyIf set
+        task.execute()
+        assertTrue(worked)
+    }
+
+}
\ No newline at end of file
Index: src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java
===================================================================
--- src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java	(revision 1620)
+++ src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java	(working copy)
@@ -440,4 +440,42 @@
         getTask().setDescription(testDescription);
         assertEquals(testDescription, getTask().getDescription());
     }
+
+    @Test
+    public void testExecutionEnabledByOptimization() {
+        final AbstractTask task = getTask();
+        task.deleteAllActions();
+        final List<Boolean> worked = new ArrayList<Boolean>();
+
+        task.doLast(new TaskAction() {
+            public void execute(Task task) {
+                worked.add(Boolean.TRUE);
+            }
+        });
+        task.onlyIf(new OptimizationAction() {
+            public boolean execute(OptimizationContext opt) {
+                assertEquals(task, opt.getTask());
+                return true;
+            }
+        });
+        task.execute();
+        assertTrue(!worked.isEmpty());
+    }
+
+    @Test
+    public void testExecutionDisabledByOptimization() {
+        final AbstractTask task = getTask();
+        task.deleteAllActions();
+
+        task.doLast(new TaskAction() {
+            public void execute(Task task) {
+                fail("Optimization failed");
+            }
+        });
+        task.onlyIf(new OptimizationAction() {
+            public boolean execute(OptimizationContext opt) { return false; }
+        });
+        task.execute();
+    }
+
 }
Index: src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
===================================================================
--- src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java	(revision 1620)
+++ src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java	(working copy)
@@ -24,8 +24,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
 
 
+
 /**
  * @author Hans Dockter
  */
@@ -49,17 +51,29 @@
         return compile;
     }
 
-    @Test
-    public void testExecute() {
+    public void testExecute(final int numFilesCompiled) {
         setUpMocksAndAttributes(compile);
         context.checking(new Expectations() {{
             one(antCompileMock).execute(compile.getSrcDirs(), compile.getIncludes(), compile.getExcludes(), compile.getDestinationDir(),
                     compile.getClasspath(), compile.getSourceCompatibility(), compile.getTargetCompatibility(), compile.getOptions(),
                     compile.getProject().getAnt());
+            one(antCompileMock).getNumFilesCompiled(); will(returnValue(numFilesCompiled));
         }});
         compile.execute();
     }
 
+    @Test
+    public void testExecuteDoingWork() {
+        testExecute(7);
+        assertTrue(compile.getDidWork());
+    }
+
+    @Test
+    public void testExecuteNotDoingWork() {
+        testExecute(0);
+        assertFalse(compile.getDidWork());
+    }
+
     // todo We need to do this to make the compiler happy. We need to file a Jira to Groovy.
     public Compile getCompile() {
         return compile;
Index: src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
===================================================================
--- src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java	(revision 1620)
+++ src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java	(working copy)
@@ -70,8 +70,7 @@
         return testObj;
     }
 
-    @Test
-    public void testExecute() {
+    public void testExecute(final int numFilesCompiled) {
         setUpMocksAndAttributes(testObj, TEST_GROOVY_CLASSPATH);
         context.checking(new Expectations() {
             {
@@ -80,12 +79,25 @@
                         testObj.getGroovyJavaExcludes(), testObj.getDestinationDir(), TEST_DEPENDENCY_MANAGER_CLASSPATH, 
                         testObj.getSourceCompatibility(), testObj.getTargetCompatibility(), testObj.getGroovyOptions(),
                         testObj.getOptions(), TEST_GROOVY_CLASSPATH);
+                one(antGroovycCompileMock).getNumFilesCompiled();  will(returnValue(numFilesCompiled));
             }
         });
         testObj.execute();
     }
 
     @Test
+    public void testExecuteDoingWork() {
+        testExecute(7);
+        assertTrue(testObj.getDidWork());
+    }
+
+    @Test
+    public void testExecuteNotDoingWork() {
+        testExecute(0);
+        assertFalse(testObj.getDidWork());
+    }
+
+    @Test
     public void testExecuteWithEmptyGroovyClasspath() {
         setUpMocksAndAttributes(testObj, Collections.emptyList());
         try {
@@ -102,6 +114,7 @@
 
         final FileCollection groovyClasspathCollection = context.mock(FileCollection.class);
         context.checking(new Expectations(){{
+            one(antJavacCompileMock).getNumFilesCompiled();
             allowing(groovyClasspathCollection).getFiles();
             will(returnValue(new LinkedHashSet(groovyClasspath)));
         }});
Index: src/test/groovy/org/gradle/integtests/SamplesJavaOptimizationIntegrationTest.groovy
===================================================================
--- src/test/groovy/org/gradle/integtests/SamplesJavaOptimizationIntegrationTest.groovy	(revision 0)
+++ src/test/groovy/org/gradle/integtests/SamplesJavaOptimizationIntegrationTest.groovy	(revision 0)
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests
+
+import org.junit.runner.RunWith
+import org.junit.Test
+
+@RunWith (DistributionIntegrationTestRunner.class)
+public class SamplesJavaOptimizationIntegrationTest {
+    // Injected by test runner
+    private GradleDistribution dist;
+    private GradleExecuter executer;
+
+
+    /**
+     * runs a build 3 times.
+     * execute clean dists
+     * check worked correctly
+     *
+     * remove test results
+     * execute dists
+     * check didn't re-run tests
+     *
+     * remove class file
+     * execute dists
+     * check that it re-ran tests 
+     */
+    @Test public void testOptimizedBuild() {
+        File javaprojectDir = new File(dist.samplesDir, 'java/optimization')
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'dists').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        // Check jar exists
+        assertExists(javaprojectDir, "build/libs/optimization-1.0.jar")
+
+        // remove test results
+        removeFile(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        removeFile(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        executer.inDirectory(javaprojectDir).withTasks('dists').run()
+
+        // assert that tests did not run
+        // (since neither compile nor compileTests should have done anything)
+        assertDoesNotExist(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertDoesNotExist(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        // remove a compiled class file
+        removeFile(javaprojectDir, 'build/classes/org/gradle/Person.class')
+
+        executer.inDirectory(javaprojectDir).withTasks('dists').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+    }
+
+    @Test public void testNoOptCommandLine() {
+        File javaprojectDir = new File(dist.samplesDir, 'java/optimization')
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'dists').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        // remove test results
+        removeFile(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        removeFile(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        executer.inDirectory(javaprojectDir).withTasks('dists').withArguments('-o').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+    }
+
+
+    private static void assertExists(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertExists()
+    }
+
+    private static void assertDoesNotExist(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertDoesNotExist()
+    }
+
+    private static void removeFile(File baseDir, String path) {
+        new TestFile(baseDir).file(path).asFile().delete();
+    }
+}
\ No newline at end of file
