overwatering.org

blog

about

Calatrava has a reasonably complex build system to enforce its opinions. One of these opinions is that the environment you’re building for should be configurable. You set the environment by setting the CALATRAVA_ENV environment variable. But what if you change it between runs? Some things should be re-built, but what? And what if you didn’t change it between runs? How do you make part of your build depend on the transient value of an environment variable?

What about file sets? Calatrava includes lists of JavaScript files to load for different platforms. It’s easy enough to make sure new files are added to these lists (modification times will usually make a rebuild happen — but not always.) But how do you remove a deleted file?

One solution is a to always perform a clean re-build. This works, but is slow and in-elegant. Inspired by the custom build system at a previous job, I’ve added a transient task feature to Rake as used by Calatrava.

require 'digest/sha1'

module Rake
  module DSL

    def transient(name, value)
      transients = File.join('.rake', 'transients')
      FileUtils.mkdir_p transients
      value_file = File.join(transients, name.to_s)
      value_hash = Digest::SHA1.hexdigest(value.to_s)
      if File.exists? value_file
        previous_hash = IO.read(value_file)
        FileUtils.rm value_file if previous_hash != value_hash
      end

      file value_file do
        File.open(value_file, "w+") { |f| f.print value_hash }
      end
      task name => value_file
    end

  end
end

You create a transient task with the transient method, and then use that as you would any other dependency.

transient :calatrava_env, ENV['CALATRAVA_ENV']
task :build => :calatrava_env

This works by tracking a hash of the value in a file under a .rake directory at the same level as your Rakefile. If the value changes, then the file is deleted. A file task will re-create the file by writing the new hash. A regular task depends on the existence of the file. To cause anything to run when your transient value changes, just depend on that intermediate task.

I’d like to look into getting this added to Rake directly at some point, but in the meantime feel free to use it in your projects.