What difference between configuration setting and doFirst (doLast)

Please explain difference between case when I just write closure inside of some task and when I write some code inside of doLast() doFirst for the same task?
For example what difference between

task cleanImages(type : clean) {
doLast {
def tree = fileTree("$rootDir")
tree.include '*.png’
tree.each { it.delete() }
}
}

and

task cleanImages(type : clean) {
def tree = fileTree("$rootDir")
tree.include '*.png’
tree.each { it.delete() }
}

When every of them will be run?

You should read the “Gradle User Guide”. This comes as a PDF in the full distribution, or you can read it online at https://docs.gradle.org/current/userguide/userguide.html . For your specific issue, read chapter 22, “The Build Lifecycle”.

David, thanks, I’ve read this part. Maybe I so stupid to understand. Or maybe it’s written on unclear manner. I’m not sure.
But I still don’t understand what difference between ‘just add settings for configuration’ and ‘run doLast()’. Can you explain if you know? Thanks.

Configuration code is used to tell the task what to do if the user chooses to run this task. It goes directly under the task closure.

Execution code is what is run when the user chooses to execute the task. It goes into the doLast closure.

task someTask {
  println("hello, I'm always run, whatever the task you choose to execute")
  // here's the code that should be executed if the user chooses to execute this task
  doLast { 
    println("hello, I'm the code that is only executed if the user chooses to execute this task")
  }
}

So, your second example is wrong, because if you run gradle anyOtherTaskThanClean, or just, for example, gradle tasks, then it will delete all the png files.

So it should be done like this?

task cleanImages(type : clean) {
def tree = fileTree("$rootDir")
tree.include ‘*.png’
}

cleanImages {
doLast {
tree.each { it.delete() }
}