Tuesday, February 1, 2011

Using Sass in an Ant build

I recently started using Sass in a side project I've been working on.  It allows me to do two neat things:

  1. Declare my site's colors as variables and re-use them everywhere.
  2. Use only meaningful classes in my markup and style those classes appropriately without copying and pasting a lot of CSS.  For example, if I want an invoice to be displayed in a groupbox, I can put the invoice data inside of a div and assign "invoice" as its class.  Then I can use the Sass mixin feature to say that the invoice class should be styled like a groupbox, whose style I define only once.

However, Sass is a Ruby tool and I'm working in Java.  The project uses Ant for its builds and I wanted a way of compiling my Sass code into CSS in an automated way.  What I ended up doing was using Ant's <apply> task to find all the .sass and .scss files in my project and run them through the Sass processor.  This is what the task ended up looking like:

<target name="sass-compile" depends="properties">
    <apply executable="sass" dest="${project.src.dir}" verbose="true" force="true" failonerror="true">
        <srcfile />
        <targetfile />

        <fileset dir="${project.src.dir}" includes="**/*.scss,**/*.sass" excludes="**/_*" />
        <firstmatchmapper>
            <globmapper from="*.sass" to="*.css" />
            <globmapper from="*.scss" to="*.css" />
        </firstmatchmapper>
    </apply>
</target>

Everything is pretty straight forward with one possible exception.  By convention, partials (shared files that are only intended to be imported by other Sass files) begin with an underscore.  I excluded them from the fileset since they aren't intended to be used directly and my build automatically rolls CSS files into the .war file.

But there's one more import-related caveat.  When used with a mapper, the <apply> task only runs the executable on input files that have been modified more recently than the corresponding destination file.  That's a problem because a Sass file might not have changed but one of its imports could have, in which case it should be re-compiled.  To make sure the build happens every time, I used force="true".

6 comments:

  1. This is extremely helpful. Thanks. Do you know how to recursively create directories if they don't exist in the task? Can't quite figure that out...

    ReplyDelete
  2. Without an example, I'm not sure I understand the question. The mkdir task will create a directory, as well as any parent directories that don't exist.
    http://ant.apache.org/manual/Tasks/mkdir.html
    Or did you want/need to create a directory structure in a more dynamic way?

    ReplyDelete
  3. Which jar needs to be class path for this?

    ReplyDelete
    Replies
    1. No JAR's are needed on the classpath. This approach uses core Ant features to find files SASS and SCSS files in your project and run the actual Sass executable on them, the same way you might from a terminal or command line. There are no extra Java libraries to add to the classpath, but the system running your Ant script needs to have Sass installed and accessible from the command line.

      Delete
  4. BUILD FAILED
    C:\...\build.xml:8: Execute failed: java.io.IOException: Cannot run program "sass" (in directory "C:\....\"): CreateProcess error=2, The system cannot find the file specified.

    I can't figure out what's the problem, the /bin ruby directory is at my windows path, and my workspace directory's all right. What's going wrong?

    ReplyDelete