You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by bo...@apache.org on 2010/11/29 06:33:40 UTC

svn commit: r1040014 - in /buildr/trunk: CHANGELOG lib/buildr/core/cc.rb spec/core/cc_spec.rb

Author: boisvert
Date: Mon Nov 29 05:33:39 2010
New Revision: 1040014

URL: http://svn.apache.org/viewvc?rev=1040014&view=rev
Log:
BUILDR-551 Continuous compilation not working for project trees

Modified:
    buildr/trunk/CHANGELOG
    buildr/trunk/lib/buildr/core/cc.rb
    buildr/trunk/spec/core/cc_spec.rb

Modified: buildr/trunk/CHANGELOG
URL: http://svn.apache.org/viewvc/buildr/trunk/CHANGELOG?rev=1040014&r1=1040013&r2=1040014&view=diff
==============================================================================
--- buildr/trunk/CHANGELOG (original)
+++ buildr/trunk/CHANGELOG Mon Nov 29 05:33:39 2010
@@ -14,6 +14,7 @@
           with classifier
 * Fixed:  BUILDR-522 Send notifications when continuous compilation
           succeeds/fails.
+* Fixed:  BUILDR-551 Continuous compilation not working for project trees
 * Change: Upgrade to Groovy 1.7.5
 * Change: BUILDR-545 Add the ability to specify the description element in in
           application.xml contained within an ear.

Modified: buildr/trunk/lib/buildr/core/cc.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/lib/buildr/core/cc.rb?rev=1040014&r1=1040013&r2=1040014&view=diff
==============================================================================
--- buildr/trunk/lib/buildr/core/cc.rb (original)
+++ buildr/trunk/lib/buildr/core/cc.rb Mon Nov 29 05:33:39 2010
@@ -33,6 +33,15 @@ module Buildr
 
   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
@@ -40,7 +49,7 @@ module Buildr
     def monitor_and_compile
       # we don't want to actually fail if our dependencies don't succeed
       begin
-        [:compile, 'test:compile'].each { |name| project.task(name).invoke }
+        each_project { |p| p.test.compile.invoke }
         build_completed(project)
       rescue Exception => ex
         $stderr.puts $terminal.color(ex.message, :red)
@@ -48,60 +57,54 @@ module Buildr
 
         build_failed(project, ex)
       end
-      main_dirs = project.compile.sources.map(&:to_s)
-      test_dirs = project.task('test:compile').sources.map(&:to_s)
-      res_dirs = project.resources.sources.map(&:to_s)
-
-      source_ext = lambda { |compiler| Array(Buildr::Compiler.select(compiler).source_ext).map(&:to_s) }
-      main_ext = source_ext.call(project.compile.compiler) unless project.compile.compiler.nil?
-      test_ext = source_ext.call(project.task('test:compile').compiler) unless project.task('test:compile').compiler.nil?
-
-      test_tail = if test_dirs.empty? then '' else ",{#{test_dirs.join ','}}/**/*.{#{test_ext.join ','}}" end
-      res_tail = if res_dirs.empty? then '' else ",{#{res_dirs.join ','}}/**/*" end
 
-      pattern = "{{#{main_dirs.join ','}}/**/*.{#{main_ext.join ','}}#{test_tail}#{res_tail}}"
-
-      times, _ = check_mtime pattern, {}     # establish baseline
-
-      dir_names = (main_dirs + test_dirs + res_dirs).map { |file| strip_filename project, file }
-      if dir_names.length == 1
-        info "Monitoring directory: #{dir_names.first}"
+      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: [#{dir_names.join ', '}]"
+        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
-      trace "Monitoring extensions: [#{main_ext.join ', '}]"
+
+      old_times = timestamps.call()
 
       while true
         sleep delay
 
-        times, changed = check_mtime pattern, times
+        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 #{strip_filename project, file}"
-          end
-
-          in_main = main_dirs.any? do |dir|
-            changed.any? { |file| file.index(dir) == 0 }
-          end
-
-          in_test = test_dirs.any? do |dir|
-            changed.any? { |file| file.index(dir) == 0 }
+            info "Detected changes in #{file}"
           end
 
-          in_res = res_dirs.any? do |dir|
-            changed.any? { |file| file.index(dir) == 0 }
+          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
 
-          project.task(:compile).reenable if in_main
-          project.task('test:compile').reenable if in_test || in_main
-
           successful = true
           begin
-            project.task(:resources).filter.run if in_res
-            project.task(:compile).invoke if in_main
-            project.task('test:compile').invoke if in_test || in_main
+            each_project { |p| p.test.compile.invoke }
             build_completed(project)
           rescue Exception => ex
             $stderr.puts $terminal.color(ex.message, :red)
@@ -122,27 +125,20 @@ module Buildr
       Buildr.application.build_failed('Compilation failed', project.path_to, ex)
     end
 
-    def check_mtime(pattern, old_times)
-      times = {}
+    def changed(new_times, old_times)
       changed = []
-
-      Dir.glob pattern do |fname|
-        times[fname] = File.mtime fname
-        if old_times[fname].nil? || old_times[fname] < File.mtime(fname)
+      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 times.has_key? fname
+        changed << fname unless new_times.has_key? fname
       end
 
-      [times, changed]
-    end
-
-    def strip_filename(project, name)
-      name.gsub project.base_dir + File::SEPARATOR, ''
+      changed
     end
   end
 
@@ -157,7 +153,6 @@ module Buildr
     before_define do |project|
       cc = CCTask.define_task :cc
       cc.send :associate_with, project
-      project.recursive_task(:cc)
     end
 
     def cc

Modified: buildr/trunk/spec/core/cc_spec.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/spec/core/cc_spec.rb?rev=1040014&r1=1040013&r2=1040014&view=diff
==============================================================================
--- buildr/trunk/spec/core/cc_spec.rb (original)
+++ buildr/trunk/spec/core/cc_spec.rb Mon Nov 29 05:33:39 2010
@@ -17,50 +17,38 @@
 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))
 
 module CCHelper
-  def setup_cc
-    project = define('foo')
 
-    cc = project.cc
-    project.stub!(:task).with(:cc).and_return(cc)
-
-    compile = mock 'compile'
-    project.stub!(:task).with(:compile).and_return(compile)
-
-    test_compile = mock 'test:compile'
-    project.stub!(:task).with('test:compile').and_return(test_compile)
-
-    filter = mock('resources').tap do |resources|
-      project.stub!(:task).with(:resources).and_return(resources)
-
-      back = mock 'filter'
-      resources.stub!(:filter).and_return(back)
-
-      back
+  # monkey-patch task instance to track number of times it is run
+  def instrument_task(task)
+    class << task
+      attr_accessor :run_count
     end
-
-    sources
-    tests
-    resources
-
-    [ project, compile, test_compile, filter ]
+    task.run_count = 0
+    task.enhance do |t|
+      t.run_count += 1
+    end
+    task
   end
 
-  def sources
-    @sources ||= ['Test1.java', 'Test2.java'].map { |f| File.join('src/main/java/thepackage', f) }.
-      each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" }
+  def instrument_project(project)
+    instrument_task project.compile
+    instrument_task project.test.compile
+    instrument_task project.resources
+    project
   end
 
-  def tests
-    @tests ||= ['Test1.java', 'Test2.java'].map { |f| File.join('src/test/java/thepackage', f) }.
-      each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" }
+  def define_foo()
+    @foo = define('foo')
+    instrument_project @foo
+    @foo
   end
 
-  def resources
-    @resources ||= ['Test1.html', 'Test2.html'].map { |f| File.join('src/main/resources', f) }.
-      each { |src| write src, '<html></html>' }
+  def foo()
+    @foo
   end
 end
 
+
 describe Buildr::CCTask do
   include CCHelper
 
@@ -69,101 +57,168 @@ describe Buildr::CCTask do
   end
 
   it 'should compile and test:compile on initial start' do
-    project, compile, test_compile, filter = setup_cc
+    ['Test1.java', 'Test2.java'].map { |f| File.join('src/main/java/thepackage', f) }.
+      each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" }
 
-    compile.should_receive :invoke
-    test_compile.should_receive :invoke
-    filter.should_not_receive :run
+    ['Test1.java', 'Test2.java'].map { |f| File.join('src/test/java/thepackage', f) }.
+      each { |src| write src, "package thepackage; class #{src.pathmap('%n')} {}" }
+
+    ['Test1.html', 'Test2.html'].map { |f| File.join('src/main/resources', f) }.
+      each { |src| write src, '<html></html>' }
+
+    define_foo()
 
     thread = Thread.new do
-      project.cc.invoke
+      foo.cc.invoke
     end
 
-    sleep 0.5
+    sleep 1
+
+    foo.compile.run_count.should == 1
+    foo.test.compile.run_count.should == 1
 
     thread.exit
   end
 
   it 'should detect a file change' do |spec|
-    
+    write 'src/main/resources/my.properties', "# comment"
     write 'src/main/java/Example.java', "public class Example {}"
     write 'src/test/java/ExampleTest.java', "public class ExampleTest {}"
-    
-    project = define("foo")
-    compile = project.compile
-    test_compile = project.test.compile
-    filter = project.resources
-    
-    # After first period:
-    compile.should_receive :invoke
-    test_compile.should_receive :invoke
-    filter.should_not_receive :run
-    
+
+    define_foo
+
     thread = Thread.new do
       begin
-        project.cc.invoke
+        foo.cc.invoke
       rescue => e
         p "unexpected exception #{e.inspect}"
         p e.backtrace.join("\n").inspect
       end
     end
-    
-    sleep 0.5
-    
-    compile.should_receive :reenable
-    compile.should_receive :invoke
 
-    test_compile.should_receive :reenable
-    test_compile.should_receive :invoke
+    sleep 1
+
+    foo.compile.run_count.should == 1
+    foo.test.compile.run_count.should == 1
+    foo.resources.run_count.should == 1
 
-    filter.should_not_receive :run
-    
     sleep 1 # Wait one sec as the timestamp needs to be different.
+
     touch File.join(Dir.pwd, 'src/main/java/Example.java')
-    sleep 0.3# Wait one standard delay and half
-    
+
+    sleep 1
+
+    foo.compile.run_count.should == 2
+    foo.test.compile.run_count.should == 2
+    foo.resources.run_count.should == 2
+
     thread.exit
   end
-  
-  
+
   it 'should support subprojects' do |spec|
-    
     write 'foo/src/main/java/Example.java', "public class Example {}"
     write 'foo/src/test/java/ExampleTest.java', "public class ExampleTest {}"
-    
+
     define 'container' do
       define('foo')
     end
-    
-    project = project("container:foo")
-    compile = project.compile
-    test_compile = project.test.compile
-    filter = project.resources
-    
-    # After first period:
-    compile.should_receive :invoke
-    test_compile.should_receive :invoke
-    filter.should_not_receive :run
-    
+
+    foo = instrument_project project("container:foo")
+
     thread = Thread.new do
-      project("container").cc.invoke
+      begin
+        project("container").cc.invoke
+      rescue => e
+        p "unexpected exception #{e.inspect}"
+        p e.backtrace.join("\n").inspect
+      end
     end
-    
-    sleep 0.5
-    
-    # After we changed the file:
-    compile.should_receive :reenable
-    compile.should_receive :invoke
-
-    test_compile.should_receive :reenable
-    test_compile.should_receive :invoke
-    
-    filter.should_not_receive :run
-    
-    sleep 1 # Wait one sec as the timestamp needs to be different.
+
+    sleep 1
+
+    foo.compile.run_count.should == 1
+    foo.test.compile.run_count.should == 1
+    foo.resources.run_count.should == 1
+
+    file("foo/target/classes/Example.class").should exist
+    tstamp = File.mtime("foo/target/classes/Example.class")
     touch File.join(Dir.pwd, 'foo/src/main/java/Example.java')
-    sleep 0.3 # Wait one standard delay and half
-    
+
+    sleep 1
+
+    foo.compile.run_count.should == 2
+    foo.test.compile.run_count.should == 2
+    foo.resources.run_count.should == 2
+    File.mtime("foo/target/classes/Example.class").should_not == tstamp
+
+    thread.exit
+  end
+
+  it 'should support parent and subprojects' do |spec|
+    write 'foo/src/main/java/Example.java', "public class Example {}"
+    write 'foo/src/test/java/ExampleTest.java', "public class ExampleTest {}"
+
+    write 'bar/src/main/java/Example.java', "public class Example {}"
+    write 'bar/src/test/java/ExampleTest.java', "public class ExampleTest {}"
+
+    write 'src/main/java/Example.java', "public class Example {}"
+    write 'src/test/java/ExampleTest.java', "public class ExampleTest {}"
+
+    write 'src/main/resources/foo.txt', "content"
+
+    define 'container' do
+      define('foo')
+      define('bar')
+    end
+
+    all = projects("container", "container:foo", "container:bar")
+    all.each { |p| instrument_project(p) }
+
+    thread = Thread.new do
+      begin
+        project("container").cc.invoke
+      rescue => e
+        p "unexpected exception #{e.inspect}"
+        p e.backtrace.join("\n").inspect
+      end
+    end
+
+    sleep 2
+
+    all.each do |p|
+      p.compile.run_count.should == 1
+      p.test.compile.run_count.should == 1
+      p.resources.run_count.should == 1
+    end
+
+    file("foo/target/classes/Example.class").should exist
+    tstamp = File.mtime("foo/target/classes/Example.class")
+
+    touch 'foo/src/main/java/Example.java'
+    p "after touch"
+    sleep 2
+
+    project("container:foo").tap do |p|
+      p.compile.run_count.should == 2
+      p.test.compile.run_count.should == 2
+      p.resources.run_count.should == 2
+    end
+    project("container").tap do |p|
+      p.compile.run_count.should == 1 # not_needed
+      p.test.compile.run_count.should == 1  # not_needed
+      p.resources.run_count.should == 2
+    end
+    File.mtime("foo/target/classes/Example.class").should_not == tstamp
+
+    touch 'src/main/java/Example.java'
+    sleep 2
+
+    project("container").tap do |p|
+      p.compile.run_count.should == 2
+      p.test.compile.run_count.should == 2
+      p.resources.run_count.should == 3
+    end
+
     thread.exit
   end
 end