Discussion:
Help with CompileCoffeeScript task
Howard Lewis Ship
2012-06-25 16:39:08 UTC
Permalink
I've been working on a little script to assist with compiling CoffeeScript
for my project. I have it partially working, but am seeking some help on
making it completely correct.

Here's the main code:

coffeescript.gradle:
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*

buildscript {
repositories { mavenCentral() }
dependencies {
classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
}
}

class CompileCoffeeScript extends DefaultTask {
def srcDir = "src/main/coffeescript"

def outputDir = "${project.buildDir}/compiled-coffeescript"

@InputDirectory
File getSrcDir() { project.file(srcDir) }

@OutputDirectory
File getOutputDir() { project.file(outputDir) }

@TaskAction
void doCompile() {
logger.info "Compiling CoffeeScript sources from $srcDir into
$outputDir"

def tree = project.fileTree srcDir, {
include '**/*.coffee'
}

tree.visit { visit ->
if (visit.directory) return

def inputFile = visit.file
def inputPath = visit.path
def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
def outputFile = new File(outputDir, outputPath)

logger.info "Compiling ${inputPath}"

outputFile.parentFile.mkdirs()

def resource = Resource.create(inputFile.absolutePath,
ResourceType.JS)

new CoffeeScriptProcessor().process(resource, inputFile.newReader(),
outputFile.newWriter())
}
}

}

project.ext.CompileCoffeeScript = CompileCoffeeScript

And here's what I've added to my main build script:

apply from: "coffeescript.gradle"

task compileCoffeeScript(type: CompileCoffeeScript)

processResources {
from compileCoffeeScript
}


This works partially: when I change a source .coffee file, or add a new
.coffee file, then all of the .coffee files are recompiled to JavaScript
and included in the output JAR file (that is, task jar depends on task
processResources which now depends on task compileCoffeeScript).

However, if I delete an input file, I'm only getting partial behavior:

:tapestry-core:compileCoffeeScript
Executing task ':tapestry-core:compileCoffeeScript' due to:
Input file
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee
for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Compiling proto/hello.coffee
:tapestry-core:processResources


... but I see the output .js file for the deleted input .coffee file still
in the JAR (and in build/compiled-coffeescript). In other words, deleting a
source file does not cause the previously generated output file to be
deleted.

Secondly, and perhaps this is related, when I change ANY .coffee file, then
ALL .coffee files are recompiled. CoffeeScript is unlike Java, each file
is pretty much independent of all others (it's all going to be very late
bound inside the client browser).

So ... should I simply delete the output directory inside my doCompile()
method? Given the message in the console output above, it seems like there
could be a notification to a task that an input file was deleted and it
should ensure the corresponding output file(s) are deleted.

But if I want a more "incremental" style, am I expected to walk the output
directory and delete anything that doesn't have a corresponding source file?

And, is there a base class to extend from that handles more of this for me?
SourceTask doesn't seem to do quite what I want.

Thanks in advance for any guidance.

$ gradle --version

------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------

Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com
Robert Fischer
2012-06-25 19:14:19 UTC
Permalink
This is what "clean" is there for. It's a common problem caused by the
fact that the build system doesn't know what output files require
which other output files, or which input files produced which output
files. But, of course, we can always teach it what the rules are.

You could also add a filter in your input to exclude an input file if
the last modified time of the input file is less than the last
modified time of the output file.

If you know the mapping, you could always wipe out the bogus output
files first based in the input file names, although I don't know a
clever/declarative Gradle way of specifying that behavior. At least,
not off the top of my head...there might be some clever filtering
manipulation you could perform.

If you want to add that code, though, you're hitting "plugin" levels
of complexity pretty quick here.

~~ Robert.
Post by Howard Lewis Ship
I've been working on a little script to assist with compiling CoffeeScript
for my project. I have it partially working, but am seeking some help on
making it completely correct.
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*
buildscript {
  repositories {  mavenCentral() }
  dependencies {
    classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
  }
}
class CompileCoffeeScript extends DefaultTask {
  def srcDir = "src/main/coffeescript"
  def outputDir = "${project.buildDir}/compiled-coffeescript"
  File getSrcDir() {  project.file(srcDir) }
  File getOutputDir() {  project.file(outputDir) }
  void doCompile() {
    logger.info "Compiling CoffeeScript sources from $srcDir into
$outputDir"
    def tree = project.fileTree srcDir, {
      include '**/*.coffee'
    }
    tree.visit { visit ->
      if (visit.directory) return
      def inputFile = visit.file
      def inputPath = visit.path
      def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
      def outputFile = new File(outputDir, outputPath)
      logger.info "Compiling ${inputPath}"
      outputFile.parentFile.mkdirs()
      def resource = Resource.create(inputFile.absolutePath,
ResourceType.JS)
      new CoffeeScriptProcessor().process(resource, inputFile.newReader(),
outputFile.newWriter())
    }
  }
}
project.ext.CompileCoffeeScript = CompileCoffeeScript
apply from: "coffeescript.gradle"
task compileCoffeeScript(type: CompileCoffeeScript)
processResources {
  from compileCoffeeScript
}
This works partially:  when I change a source .coffee file, or add a new
.coffee file, then all of the .coffee files are recompiled to JavaScript and
included in the output JAR file (that is, task jar depends on task
processResources which now depends on task compileCoffeeScript).
:tapestry-core:compileCoffeeScript
Input file
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee
for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Compiling proto/hello.coffee
:tapestry-core:processResources
... but I see the output .js file for the deleted input .coffee file still
in the JAR (and in build/compiled-coffeescript). In other words, deleting a
source file does not cause the previously generated output file to be
deleted.
Secondly, and perhaps this is related, when I change ANY .coffee file, then
ALL .coffee files are recompiled.  CoffeeScript is unlike Java, each file is
pretty much independent of all others (it's all going to be very late bound
inside the client browser).
So ... should I simply delete the output directory inside my doCompile()
method?  Given the message in the console output above, it seems like there
could be a notification to a task that an input file was deleted and it
should ensure the corresponding output file(s) are deleted.
But if I want a more "incremental" style, am I expected to walk the output
directory and delete anything that doesn't have a corresponding source file?
And, is there a base class to extend from that handles more of this for me?
SourceTask doesn't seem to do quite what I want.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn
how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
Howard Lewis Ship
2012-06-25 22:31:07 UTC
Permalink
On Mon, Jun 25, 2012 at 12:14 PM, Robert Fischer <
Post by Robert Fischer
This is what "clean" is there for. It's a common problem caused by the
fact that the build system doesn't know what output files require
which other output files, or which input files produced which output
files. But, of course, we can always teach it what the rules are.
I'm not asking that build system to magically know; I'd assume that mapping
would be trapped inside a bit of my code. I'm slightly off-put by the
direction to use "clean"; as I understood it, part of the appeal of Gradle
is never having to use "clean".
Post by Robert Fischer
You could also add a filter in your input to exclude an input file if
the last modified time of the input file is less than the last
modified time of the output file.
That's kind of where I'm headed, though for the meantime, I'm deleting the
output directory early, and just re-building all .js from all .coffee.
Post by Robert Fischer
If you know the mapping, you could always wipe out the bogus output
files first based in the input file names, although I don't know a
clever/declarative Gradle way of specifying that behavior. At least,
not off the top of my head...there might be some clever filtering
manipulation you could perform.
I'm kind of picturing some additional method annotations for method to be
invoked when a source file from a previous build no longer exists. That
would be very handy, since my code could re-do the mapping from input file
to output file and delete the output file.
Post by Robert Fischer
If you want to add that code, though, you're hitting "plugin" levels
of complexity pretty quick here.
My goal is to turn this into a plugin ... except that it looks like 1.1
will have (experimental) support for this out-of-the-box.
Post by Robert Fischer
~~ Robert.
Post by Howard Lewis Ship
I've been working on a little script to assist with compiling
CoffeeScript
Post by Howard Lewis Ship
for my project. I have it partially working, but am seeking some help on
making it completely correct.
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*
buildscript {
repositories { mavenCentral() }
dependencies {
classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
}
}
class CompileCoffeeScript extends DefaultTask {
def srcDir = "src/main/coffeescript"
def outputDir = "${project.buildDir}/compiled-coffeescript"
@InputDirectory
File getSrcDir() { project.file(srcDir) }
@OutputDirectory
File getOutputDir() { project.file(outputDir) }
@TaskAction
void doCompile() {
logger.info "Compiling CoffeeScript sources from $srcDir into
$outputDir"
def tree = project.fileTree srcDir, {
include '**/*.coffee'
}
tree.visit { visit ->
if (visit.directory) return
def inputFile = visit.file
def inputPath = visit.path
def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
def outputFile = new File(outputDir, outputPath)
logger.info "Compiling ${inputPath}"
outputFile.parentFile.mkdirs()
def resource = Resource.create(inputFile.absolutePath,
ResourceType.JS)
new CoffeeScriptProcessor().process(resource,
inputFile.newReader(),
Post by Howard Lewis Ship
outputFile.newWriter())
}
}
}
project.ext.CompileCoffeeScript = CompileCoffeeScript
apply from: "coffeescript.gradle"
task compileCoffeeScript(type: CompileCoffeeScript)
processResources {
from compileCoffeeScript
}
This works partially: when I change a source .coffee file, or add a new
.coffee file, then all of the .coffee files are recompiled to JavaScript
and
Post by Howard Lewis Ship
included in the output JAR file (that is, task jar depends on task
processResources which now depends on task compileCoffeeScript).
:tapestry-core:compileCoffeeScript
Input file
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee
Post by Howard Lewis Ship
for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Post by Howard Lewis Ship
Compiling proto/hello.coffee
:tapestry-core:processResources
... but I see the output .js file for the deleted input .coffee file
still
Post by Howard Lewis Ship
in the JAR (and in build/compiled-coffeescript). In other words,
deleting a
Post by Howard Lewis Ship
source file does not cause the previously generated output file to be
deleted.
Secondly, and perhaps this is related, when I change ANY .coffee file,
then
Post by Howard Lewis Ship
ALL .coffee files are recompiled. CoffeeScript is unlike Java, each
file is
Post by Howard Lewis Ship
pretty much independent of all others (it's all going to be very late
bound
Post by Howard Lewis Ship
inside the client browser).
So ... should I simply delete the output directory inside my doCompile()
method? Given the message in the console output above, it seems like
there
Post by Howard Lewis Ship
could be a notification to a task that an input file was deleted and it
should ensure the corresponding output file(s) are deleted.
But if I want a more "incremental" style, am I expected to walk the
output
Post by Howard Lewis Ship
directory and delete anything that doesn't have a corresponding source
file?
Post by Howard Lewis Ship
And, is there a base class to extend from that handles more of this for
me?
Post by Howard Lewis Ship
SourceTask doesn't seem to do quite what I want.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to
learn
Post by Howard Lewis Ship
how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com
Luke Daley
2012-06-26 00:24:00 UTC
Permalink
Post by Robert Fischer
This is what "clean" is there for. It's a common problem caused by the
fact that the build system doesn't know what output files require
which other output files, or which input files produced which output
files. But, of course, we can always teach it what the rules are.
I'm not asking that build system to magically know; I'd assume that mapping would be trapped inside a bit of my code. I'm slightly off-put by the direction to use "clean"; as I understood it, part of the appeal of Gradle is never having to use "clean".
Correct, no build should require "clean" for reproducibility.
Post by Robert Fischer
You could also add a filter in your input to exclude an input file if
the last modified time of the input file is less than the last
modified time of the output file.
That's kind of where I'm headed, though for the meantime, I'm deleting the output directory early, and just re-building all .js from all .coffee.
If you know the mapping, you could always wipe out the bogus output
files first based in the input file names, although I don't know a
clever/declarative Gradle way of specifying that behavior. At least,
not off the top of my head...there might be some clever filtering
manipulation you could perform.
I'm kind of picturing some additional method annotations for method to be invoked when a source file from a previous build no longer exists. That would be very handy, since my code could re-do the mapping from input file to output file and delete the output file.
If you want to add that code, though, you're hitting "plugin" levels
of complexity pretty quick here.
My goal is to turn this into a plugin ... except that it looks like 1.1 will have (experimental) support for this out-of-the-box.
~~ Robert.
Post by Howard Lewis Ship
I've been working on a little script to assist with compiling CoffeeScript
for my project. I have it partially working, but am seeking some help on
making it completely correct.
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*
buildscript {
repositories { mavenCentral() }
dependencies {
classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
}
}
class CompileCoffeeScript extends DefaultTask {
def srcDir = "src/main/coffeescript"
def outputDir = "${project.buildDir}/compiled-coffeescript"
@InputDirectory
File getSrcDir() { project.file(srcDir) }
@OutputDirectory
File getOutputDir() { project.file(outputDir) }
@TaskAction
void doCompile() {
logger.info "Compiling CoffeeScript sources from $srcDir into
$outputDir"
def tree = project.fileTree srcDir, {
include '**/*.coffee'
}
tree.visit { visit ->
if (visit.directory) return
def inputFile = visit.file
def inputPath = visit.path
def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
def outputFile = new File(outputDir, outputPath)
logger.info "Compiling ${inputPath}"
outputFile.parentFile.mkdirs()
def resource = Resource.create(inputFile.absolutePath,
ResourceType.JS)
new CoffeeScriptProcessor().process(resource, inputFile.newReader(),
outputFile.newWriter())
}
}
}
project.ext.CompileCoffeeScript = CompileCoffeeScript
apply from: "coffeescript.gradle"
task compileCoffeeScript(type: CompileCoffeeScript)
processResources {
from compileCoffeeScript
}
This works partially: when I change a source .coffee file, or add a new
.coffee file, then all of the .coffee files are recompiled to JavaScript and
included in the output JAR file (that is, task jar depends on task
processResources which now depends on task compileCoffeeScript).
:tapestry-core:compileCoffeeScript
Input file
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee
for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Compiling proto/hello.coffee
:tapestry-core:processResources
... but I see the output .js file for the deleted input .coffee file still
in the JAR (and in build/compiled-coffeescript). In other words, deleting a
source file does not cause the previously generated output file to be
deleted.
Secondly, and perhaps this is related, when I change ANY .coffee file, then
ALL .coffee files are recompiled. CoffeeScript is unlike Java, each file is
pretty much independent of all others (it's all going to be very late bound
inside the client browser).
So ... should I simply delete the output directory inside my doCompile()
method? Given the message in the console output above, it seems like there
could be a notification to a task that an input file was deleted and it
should ensure the corresponding output file(s) are deleted.
But if I want a more "incremental" style, am I expected to walk the output
directory and delete anything that doesn't have a corresponding source file?
And, is there a base class to extend from that handles more of this for me?
SourceTask doesn't seem to do quite what I want.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn
how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
Robert Fischer
2012-06-26 04:26:12 UTC
Permalink
For the record, I wasn't advocating using "clean" (see my entire
e-mail after the first sentence). I was just noting that this is the
textbook problem for which "clean" is presented as an answer.

It shouldn't be hard to hook up the compatibility you are looking for.
As you say, the one thing that would be nice is to know the names of
the source files the previous time the task was executed.

~~ Robert.
Post by Howard Lewis Ship
On Mon, Jun 25, 2012 at 12:14 PM, Robert Fischer
Post by Robert Fischer
This is what "clean" is there for. It's a common problem caused by the
fact that the build system doesn't know what output files require
which other output files, or which input files produced which output
files. But, of course, we can always teach it what the rules are.
I'm not asking that build system to magically know; I'd assume that mapping
would be trapped inside a bit of my code. I'm slightly off-put by the
direction to use "clean"; as I understood it, part of the appeal of Gradle
is never having to use "clean".
Correct, no build should require "clean" for reproducibility.
Post by Robert Fischer
You could also add a filter in your input to exclude an input file if
the last modified time of the input file is less than the last
modified time of the output file.
That's kind of where I'm headed, though for the meantime, I'm deleting the
output directory early, and just re-building all .js from all .coffee.
Post by Robert Fischer
If you know the mapping, you could always wipe out the bogus output
files first based in the input file names, although I don't know a
clever/declarative Gradle way of specifying that behavior. At least,
not off the top of my head...there might be some clever filtering
manipulation you could perform.
I'm kind of picturing some additional method annotations for method to be
invoked when a source file from a previous build no longer exists. That
would be very handy, since my code could re-do the mapping from input file
to output file and delete the output file.
Post by Robert Fischer
If you want to add that code, though, you're hitting "plugin" levels
of complexity pretty quick here.
My goal is to turn this into a plugin ... except that it looks like 1.1 will
have (experimental) support for this out-of-the-box.
Post by Robert Fischer
~~ Robert.
Post by Howard Lewis Ship
I've been working on a little script to assist with compiling CoffeeScript
for my project. I have it partially working, but am seeking some help on
making it completely correct.
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*
buildscript {
  repositories {  mavenCentral() }
  dependencies {
    classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
  }
}
class CompileCoffeeScript extends DefaultTask {
  def srcDir = "src/main/coffeescript"
  def outputDir = "${project.buildDir}/compiled-coffeescript"
  File getSrcDir() {  project.file(srcDir) }
  File getOutputDir() {  project.file(outputDir) }
  void doCompile() {
    logger.info "Compiling CoffeeScript sources from $srcDir into
$outputDir"
    def tree = project.fileTree srcDir, {
      include '**/*.coffee'
    }
    tree.visit { visit ->
      if (visit.directory) return
      def inputFile = visit.file
      def inputPath = visit.path
      def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
      def outputFile = new File(outputDir, outputPath)
      logger.info "Compiling ${inputPath}"
      outputFile.parentFile.mkdirs()
      def resource = Resource.create(inputFile.absolutePath,
ResourceType.JS)
      new CoffeeScriptProcessor().process(resource,
inputFile.newReader(),
outputFile.newWriter())
    }
  }
}
project.ext.CompileCoffeeScript = CompileCoffeeScript
apply from: "coffeescript.gradle"
task compileCoffeeScript(type: CompileCoffeeScript)
processResources {
  from compileCoffeeScript
}
This works partially:  when I change a source .coffee file, or add a new
.coffee file, then all of the .coffee files are recompiled to JavaScript and
included in the output JAR file (that is, task jar depends on task
processResources which now depends on task compileCoffeeScript).
:tapestry-core:compileCoffeeScript
Input file
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee
for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into
/Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Compiling proto/hello.coffee
:tapestry-core:processResources
... but I see the output .js file for the deleted input .coffee file still
in the JAR (and in build/compiled-coffeescript). In other words, deleting a
source file does not cause the previously generated output file to be
deleted.
Secondly, and perhaps this is related, when I change ANY .coffee file, then
ALL .coffee files are recompiled.  CoffeeScript is unlike Java, each file is
pretty much independent of all others (it's all going to be very late bound
inside the client browser).
So ... should I simply delete the output directory inside my doCompile()
method?  Given the message in the console output above, it seems like there
could be a notification to a task that an input file was deleted and it
should ensure the corresponding output file(s) are deleted.
But if I want a more "incremental" style, am I expected to walk the output
directory and delete anything that doesn't have a corresponding source file?
And, is there a base class to extend from that handles more of this for me?
SourceTask doesn't seem to do quite what I want.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn
how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
---------------------------------------------------------------------
   http://xircles.codehaus.org/manage_email
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn
how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
Luke Daley
2012-06-25 21:51:34 UTC
Permalink
I've been working on a little script to assist with compiling CoffeeScript for my project. I have it partially working, but am seeking some help on making it completely correct.
import ro.isdc.wro.model.resource.*
import ro.isdc.wro.extensions.processor.js.*
buildscript {
repositories { mavenCentral() }
dependencies {
classpath "ro.isdc.wro4j:wro4j-extensions:${versions.wro4j}"
}
}
class CompileCoffeeScript extends DefaultTask {
def srcDir = "src/main/coffeescript"
def outputDir = "${project.buildDir}/compiled-coffeescript"
@InputDirectory
File getSrcDir() { project.file(srcDir) }
@OutputDirectory
File getOutputDir() { project.file(outputDir) }
@TaskAction
void doCompile() {
logger.info "Compiling CoffeeScript sources from $srcDir into $outputDir"
def tree = project.fileTree srcDir, {
include '**/*.coffee'
}
tree.visit { visit ->
if (visit.directory) return
def inputFile = visit.file
def inputPath = visit.path
def outputPath = inputPath.replaceAll(/\.coffee$/, '.js')
def outputFile = new File(outputDir, outputPath)
logger.info "Compiling ${inputPath}"
outputFile.parentFile.mkdirs()
def resource = Resource.create(inputFile.absolutePath, ResourceType.JS)
new CoffeeScriptProcessor().process(resource, inputFile.newReader(), outputFile.newWriter())
}
}
}
project.ext.CompileCoffeeScript = CompileCoffeeScript
apply from: "coffeescript.gradle"
task compileCoffeeScript(type: CompileCoffeeScript)
processResources {
from compileCoffeeScript
}
This works partially: when I change a source .coffee file, or add a new .coffee file, then all of the .coffee files are recompiled to JavaScript and included in the output JAR file (that is, task jar depends on task processResources which now depends on task compileCoffeeScript).
:tapestry-core:compileCoffeeScript
Input file /Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/src/main/coffeescript/proto/bye.coffee for task ':tapestry-core:compileCoffeeScript' removed.
Compiling CoffeeScript sources from src/main/coffeescript into /Users/hlship/workspaces/tapestry/tapestry5/tapestry-core/build/compiled-coffeescript
Compiling proto/hello.coffee
:tapestry-core:processResources
... but I see the output .js file for the deleted input .coffee file still in the JAR (and in build/compiled-coffeescript). In other words, deleting a source file does not cause the previously generated output file to be deleted.
Secondly, and perhaps this is related, when I change ANY .coffee file, then ALL .coffee files are recompiled. CoffeeScript is unlike Java, each file is pretty much independent of all others (it's all going to be very late bound inside the client browser).
So ... should I simply delete the output directory inside my doCompile() method?
Yes, see the Compile task for example.
Given the message in the console output above, it seems like there could be a notification to a task that an input file was deleted and it should ensure the corresponding output file(s) are deleted.
That would prevent the task doing any fine grained incrementalness and would be too presumptuous.
But if I want a more "incremental" style, am I expected to walk the output directory and delete anything that doesn't have a corresponding source file?
Yes, Gradle can't know the mapping here as it is specific to what the task is doing.

In the future, Gradle will be able to give you info on what changed but you will still have to do some work. For cases like this though that are one to one it will probably be little.
And, is there a base class to extend from that handles more of this for me? SourceTask doesn't seem to do quite what I want.
No, SourceTask is your best option right now.

FYI - the nightlies have coffee script compilation support and this will be experimental in 1.1. There is no commitment at this stage on when this will be non experimental.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
Howard Lewis Ship
2012-06-25 22:36:07 UTC
Permalink
So ... should I simply delete the output directory inside my doCompile() method?
Yes, see the Compile task for example.
I've changed over to this, deleting the output directory and regenerating
all from source, and it seems to work. I want to double check the "deleted
a source file" scenario, to make sure the Copy and Jar tasks do the right
thing as well.

I suspect that I'll only end up with a few (dozen) CoffeeScript files
totally a couple of thousand lines, so I expect this to not be a terrible
problem.

BTW; is it valid to start a thread pool and do a lot of this compilation in
parallel, as long as the pool is shutdown before leaving the method? It
seems to me that most of the time being spent is going to be disk I/O
reading and writing the source files.

Of course, what I really should do is compare timestamps on the output file
to decide if I need to compile the input file at all.
Given the message in the console output above, it seems like there could
be a notification to a task that an input file was deleted and it should
ensure the corresponding output file(s) are deleted.
That would prevent the task doing any fine grained incrementalness and
would be too presumptuous.
True, but the callback could be optional or advisory; an extra annotation
on a method that says: "this source file was deleted"; the method would be
responsible for identifying the corresponding output file.
But if I want a more "incremental" style, am I expected to walk the output
directory and delete anything that doesn't have a corresponding source file?
Yes, Gradle can't know the mapping here as it is specific to what the task is doing.
In the future, Gradle will be able to give you info on what changed but
you will still have to do some work. For cases like this though that are
one to one it will probably be little.
And, is there a base class to extend from that handles more of this for
me? SourceTask doesn't seem to do quite what I want.
No, SourceTask is your best option right now.
FYI - the nightlies have coffee script compilation support and this will
be experimental in 1.1. There is no commitment at this stage on when this
will be non experimental.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
--
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com
Luke Daley
2012-06-26 00:32:19 UTC
Permalink
Post by Luke Daley
So ... should I simply delete the output directory inside my doCompile() method?
Yes, see the Compile task for example.
I've changed over to this, deleting the output directory and regenerating all from source, and it seems to work. I want to double check the "deleted a source file" scenario, to make sure the Copy and Jar tasks do the right thing as well.
I suspect that I'll only end up with a few (dozen) CoffeeScript files totally a couple of thousand lines, so I expect this to not be a terrible problem.
Which might mean that this is a micro optimization in practice.
Post by Luke Daley
BTW; is it valid to start a thread pool and do a lot of this compilation in parallel, as long as the pool is shutdown before leaving the method? It seems to me that most of the time being spent is going to be disk I/O reading and writing the source files.
Completely valid.
Post by Luke Daley
Of course, what I really should do is compare timestamps on the output file to decide if I need to compile the input file at all.
Timestamps aren't good enough as they can lie. That is, they are not enough to guarantee that you don't need to recompile. You ultimately have to compare against the previous run. Fine grained incrementalness is more challenging than it first appears.
Post by Luke Daley
Given the message in the console output above, it seems like there could be a notification to a task that an input file was deleted and it should ensure the corresponding output file(s) are deleted.
That would prevent the task doing any fine grained incrementalness and would be too presumptuous.
True, but the callback could be optional or advisory; an extra annotation on a method that says: "this source file was deleted"; the method would be responsible for identifying the corresponding output file.
Which files have been removed since last time is also the kind of richer (along with what's new or changed) information we will provide in the future.

It won't be enough to just use this though. You will also have to cross reference with how the outputs have changed.
Post by Luke Daley
But if I want a more "incremental" style, am I expected to walk the output directory and delete anything that doesn't have a corresponding source file?
Yes, Gradle can't know the mapping here as it is specific to what the task is doing.
In the future, Gradle will be able to give you info on what changed but you will still have to do some work. For cases like this though that are one to one it will probably be little.
And, is there a base class to extend from that handles more of this for me? SourceTask doesn't seem to do quite what I want.
No, SourceTask is your best option right now.
FYI - the nightlies have coffee script compilation support and this will be experimental in 1.1. There is no commitment at this stage on when this will be non experimental.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
Howard Lewis Ship
2012-06-26 01:28:20 UTC
Permalink
Are there any other changes you'd make to my script at this point? I may
blog about it. For instance, is there a reasonable/idiomatic way to merge
the srcDir and outputDir fields with the annotation properties in Gradle
1.0? I half way remember you alluding to this during the class.
Post by Howard Lewis Ship
So ... should I simply delete the output directory inside my doCompile() method?
Yes, see the Compile task for example.
I've changed over to this, deleting the output directory and regenerating
all from source, and it seems to work. I want to double check the "deleted
a source file" scenario, to make sure the Copy and Jar tasks do the right
thing as well.
I suspect that I'll only end up with a few (dozen) CoffeeScript files
totally a couple of thousand lines, so I expect this to not be a terrible
problem.
Which might mean that this is a micro optimization in practice.
BTW; is it valid to start a thread pool and do a lot of this compilation
in parallel, as long as the pool is shutdown before leaving the method? It
seems to me that most of the time being spent is going to be disk I/O
reading and writing the source files.
Completely valid.
Of course, what I really should do is compare timestamps on the output
file to decide if I need to compile the input file at all.
Timestamps aren't good enough as they can lie. That is, they are not
enough to guarantee that you don't need to recompile. You ultimately have
to compare against the previous run. Fine grained incrementalness is more
challenging than it first appears.
Given the message in the console output above, it seems like there could
be a notification to a task that an input file was deleted and it should
ensure the corresponding output file(s) are deleted.
That would prevent the task doing any fine grained incrementalness and
would be too presumptuous.
True, but the callback could be optional or advisory; an extra annotation
on a method that says: "this source file was deleted"; the method would be
responsible for identifying the corresponding output file.
Which files have been removed since last time is also the kind of richer
(along with what's new or changed) information we will provide in the
future.
It won't be enough to just use this though. You will also have to cross
reference with how the outputs have changed.
But if I want a more "incremental" style, am I expected to walk the
output directory and delete anything that doesn't have a corresponding
source file?
Yes, Gradle can't know the mapping here as it is specific to what the task is doing.
In the future, Gradle will be able to give you info on what changed but
you will still have to do some work. For cases like this though that are
one to one it will probably be little.
And, is there a base class to extend from that handles more of this for
me? SourceTask doesn't seem to do quite what I want.
No, SourceTask is your best option right now.
FYI - the nightlies have coffee script compilation support and this will
be experimental in 1.1. There is no commitment at this stage on when this
will be non experimental.
Thanks in advance for any guidance.
$ gradle --version
------------------------------------------------------------
Gradle 1.0
------------------------------------------------------------
Gradle build time: Tuesday, June 12, 2012 12:56:21 AM UTC
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
Ivy: 2.2.0
JVM: 1.7.0_04 (Oracle Corporation 23.0-b21)
OS: Mac OS X 10.7.4 x86_64
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
--
Howard M. Lewis Ship
Creator of Apache Tapestry
The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!
(971) 678-5210
http://howardlewisship.com
--
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com
Loading...