# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with this # work for additional information regarding copyright ownership. The ASF # licenses this file to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. module Buildr class CCTask < Rake::Task attr_accessor :delay attr_reader :project def initialize(*args) super @delay = 0.2 enhance do monitor_and_compile end end private # run block on sub-projects depth-first, then on this project def each_project(&block) depth_first = lambda do |p| p.projects.each { |c| depth_first.call(c, &block) } block.call(p) end depth_first.call(@project) end def associate_with(project) @project = project end def monitor_and_compile # we don't want to actually fail if our dependencies don't succeed begin each_project { |p| p.test.compile.invoke } build_completed(project) rescue Exception => ex $stderr.puts $terminal.color(ex.message, :red) $stderr.puts build_failed(project, ex) end dirs = [] each_project do |p| dirs += p.compile.sources.map(&:to_s) dirs += p.test.compile.sources.map(&:to_s) dirs += p.resources.sources.map(&:to_s) end if dirs.length == 1 info "Monitoring directory: #{dirs.first}" else info "Monitoring directories: [#{dirs.join ', '}]" end timestamps = lambda do times = {} dirs.each { |d| Dir.glob("#{d}/**/*").map { |f| times[f] = File.mtime f } } times end old_times = timestamps.call() while true sleep delay new_times = timestamps.call() changed = changed(new_times, old_times) old_times = new_times unless changed.empty? info '' # better spacing changed.each do |file| info "Detected changes in #{file}" end each_project do |p| # transitively reenable prerequisites reenable = lambda do |t| t = task(t) t.reenable t.prerequisites.each { |c| reenable.call(c) } end reenable.call(p.test.compile) end successful = true begin each_project { |p| p.test.compile.invoke } build_completed(project) rescue Exception => ex $stderr.puts $terminal.color(ex.message, :red) build_failed(project, ex) successful = false end puts $terminal.color("Build complete", :green) if successful end end end def build_completed(project) Buildr.application.build_completed('Compilation successful', project.path_to) end def build_failed(project, ex = nil) Buildr.application.build_failed('Compilation failed', project.path_to, ex) end def changed(new_times, old_times) changed = [] new_times.each do |(fname,newtime)| if old_times[fname].nil? || old_times[fname] < newtime changed << fname end end # detect deletion (slower than it could be) old_times.each_key do |fname| changed << fname unless new_times.has_key? fname end changed end end module CC include Extension first_time do desc 'Execute continuous compilation, listening to changes' Project.local_task('cc') { |name| "Executing continuous compilation for #{name}" } end before_define do |project| cc = CCTask.define_task :cc cc.send :associate_with, project end def cc task :cc end end class Project include CC end end