           cfPath = path.join cvPath, cf
           throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
           matchedFiles.push cfPath
+          matchedFileHashes.push @getFileHash(cfPath)
           builtFiles.push cf
-          if not requiresBuild and (not path.existsSync(bfPath) or
-          fs.statSync(cfPath).mtime > fs.statSync(bfPath).mtime)
-            requiresBuild = true
-      if requiresBuild
-        console.log "Building #{bf}..."
-        mkdirp.sync path.dirname(bfPath)
-        bfFile = fs.openSync bfPath, 'w'
-        for mf in matchedFiles
-          fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
+      buildFileExists = path.existsSync bfPath
+      if buildFileExists and matchedFiles.length is 0
+        console.log "Removing #{bf}..."
+        fs.unlinkSync bfPath
+      else if matchedFiles.length > 0
+        newMatchedFilesHash = @getHash matchedFileHashes.join()
+        unless path.existsSync(bfPath) and
+        buildFileHashes[bfPath] is newMatchedFilesHash
+          console.log "Building #{bf}..."
+          mkdirp.sync path.dirname(bfPath)
+          bfFile = fs.openSync bfPath, 'w'
+          for mf in matchedFiles
+            fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
+          buildFileHashes[bfPath] = newMatchedFilesHash
+          @saveDatabase version, @buildFileHashDb, buildFileHashes
       compiledFiles.push bf
     # Move files to build dir
+    copyFileHashes = @loadDatabase version, @copyFileHashDb
     ovPath = path.join(@outputPath, version)
     mkdirp.sync ovPath
     buildFileNames = _.keys @files
-    walkdir.sync cvPath, (f, stat) ->
+    walkdir.sync cvPath, (f, stat) =>
       return if stat.isDirectory()
       f = path.relative(cvPath, f)
       outf = path.join ovPath, f
       if builtFiles.indexOf(f) is -1 or buildFileNames.indexOf(f) isnt -1
         compf = path.join cvPath, f
-        if not path.existsSync(outf) or
-        fs.statSync(outf).mtime < fs.statSync(compf).mtime
+        compfHash = @getFileHash compf
+        unless path.existsSync(outf) and
+        copyFileHashes[compf] is compfHash
           # Copy
           console.log "Outputting #{f}..."
           mkdirp.sync path.dirname(outf)
           fs.writeFileSync outf, fs.readFileSync(compf)
+          copyFileHashes[compf] = compfHash
+          @saveDatabase version, @copyFileHashDb, copyFileHashes          
       else if path.existsSync(outf)
         console.log "Removing orphaned file #{f}..."
         fs.unlinkSync outf

          else
            console.log "Copying #{f}..."
            fs.writeFileSync targetFile, fs.readFileSync(sourceFile)
    # Prune orphaned files
    buildFileNames = []
    for bf in _.keys @files
      buildFileNames.push path.join(@compilePath, version, bf)
    walkdir.sync path.join(@compilePath, version), (f, stat) ->
      return if stat.isDirectory()
      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
        console.log "Removing orphaned file #{f}..."

  # Build the source tree
  setRootPath: (p) ->
    @sourcePath = path.join p, 'src'
    @tmpPath = path.join p, 'tmp'
    @outputPath = path.join p, 'build'
  build: (version) ->
    version = @defaultVersion unless version?
