Skip to content
This repository
Browse code

Added Task type to better handle needed? checks.

OnceTask is no longer needed once it has been run.
Microsecond tasks avoid 1s time resolution limitation on some file
systems (e.g. HFS+).
  • Loading branch information...
commit 31a9f21aad1f0510364406d77d9917b3fce4f805 1 parent 8e68993
Joe Yates authored
14 lib/rake/builder.rb
@@ -5,6 +5,7 @@
5 5 require 'rake/path'
6 6 require 'rake/local_config'
7 7 require 'rake/file_task_alias'
  8 +require 'rake/microsecond'
8 9 require 'rake/once_task'
9 10 require 'compiler'
10 11
@@ -265,7 +266,7 @@ def define_default
265 266 end
266 267
267 268 def define
268   - task :environment do
  269 + once_task :environment do
269 270 logger.level = Logger::DEBUG if ENV[ 'DEBUG' ]
270 271 end
271 272
@@ -281,7 +282,7 @@ def define
281 282 FileTaskAlias.define_task( :build, @target )
282 283
283 284 desc "Build '#{ target_basename }'"
284   - file @target => [ scoped_task( :environment ),
  285 + microsecond_file @target => [ scoped_task( :environment ),
285 286 scoped_task( :compile ),
286 287 *@target_prerequisites ] do | t |
287 288 shell "rm -f #{ t.name }"
@@ -294,7 +295,7 @@ def define
294 295 desc "Compile all sources"
295 296 # Only import dependencies when we're compiling
296 297 # otherwise makedepend gets run on e.g. 'rake -T'
297   - task :compile => [ scoped_task( :environment ),
  298 + once_task :compile => [ scoped_task( :environment ),
298 299 @makedepend_file,
299 300 scoped_task( :load_makedepend ),
300 301 *object_files ]
@@ -303,7 +304,7 @@ def define
303 304 define_compile_task( src )
304 305 end
305 306
306   - directory @objects_path
  307 + microsecond_directory @objects_path
307 308
308 309 file scoped_task( local_config ) do
309 310 @logger.add( Logger::DEBUG, "Creating file '#{ local_config }'" )
@@ -313,7 +314,7 @@ def define
313 314 config.save
314 315 end
315 316
316   - file @makedepend_file => [ scoped_task( :load_local_config ),
  317 + microsecond_file @makedepend_file => [ scoped_task( :load_local_config ),
317 318 scoped_task( :missing_headers ),
318 319 @objects_path,
319 320 *project_files ] do
@@ -328,7 +329,7 @@ def define
328 329 @include_paths += Rake::Path.expand_all_with_root( config.include_paths, @rakefile_path )
329 330 end
330 331
331   - task :missing_headers => [ *generated_headers ] do
  332 + once_task scoped_task( :missing_headers ) => [ *generated_headers ] do
332 333 missing_headers
333 334 end
334 335
@@ -599,6 +600,7 @@ def object_files
599 600 def project_files
600 601 source_files + header_files
601 602 end
  603 + public :project_files
602 604
603 605 def file_list( files, delimiter = ' ' )
604 606 files.join( delimiter )
70 lib/rake/microsecond.rb
... ... @@ -0,0 +1,70 @@
  1 +require 'rubygems' if RUBY_VERSION < '1.9'
  2 +require 'rake/tasklib'
  3 +require 'fileutils'
  4 +
  5 +module Rake
  6 +
  7 + module Microsecond
  8 + # Compensate for file systems with 1s resolution
  9 +
  10 + class FileTask < Task
  11 +
  12 + attr_accessor :timestamp
  13 +
  14 + def self.define_task( *args, &block )
  15 + task = super( *args, &block )
  16 + task.timestamp = nil
  17 + task
  18 + end
  19 +
  20 + def needed?
  21 + return true if ! File.exist?( self.name )
  22 + @timestamp = File.stat( self.name ).mtime if @timestamp.nil?
  23 + return self.prerequisites.any? { | n | ! application[n].timestamp.nil? && application[n].timestamp > @timestamp }
  24 + end
  25 +
  26 + def execute(*args)
  27 + @timestamp = Time.now
  28 + super(*args)
  29 + end
  30 +
  31 + end
  32 +
  33 + class DirectoryTask < Task
  34 +
  35 + include FileUtils
  36 +
  37 + attr_accessor :timestamp
  38 +
  39 + def self.define_task( *args, &block )
  40 + task = super( *args, &block )
  41 + task.timestamp = nil
  42 + task
  43 + end
  44 +
  45 + def needed?
  46 + exists = File.directory?( self.name )
  47 + @timestamp = File.stat( self.name ).mtime if exists
  48 + ! exists
  49 + end
  50 +
  51 + def execute(*args)
  52 + mkdir_p self.name, :verbose => false
  53 + @timestamp = Time.now
  54 + super(*args)
  55 + end
  56 +
  57 + end
  58 +
  59 + end
  60 +
  61 +end
  62 +
  63 +def microsecond_file(*args, &block)
  64 + Rake::Microsecond::FileTask.define_task(*args, &block)
  65 +end
  66 +
  67 +def microsecond_directory(*args, &block)
  68 + Rake::Microsecond::DirectoryTask.define_task(*args, &block)
  69 +end
  70 +
3  lib/rake/once_task.rb
@@ -7,14 +7,17 @@ module Rake
7 7 class OnceTask < Task
8 8
9 9 attr_accessor :invoked
  10 + attr_accessor :timestamp
10 11
11 12 def self.define_task( *args, &block )
12 13 task = super( *args, &block )
  14 + task.timestamp = nil
13 15 task.invoked = false
14 16 task
15 17 end
16 18
17 19 def execute(*args)
  20 + @timestamp = Time.now
18 21 @invoked = true
19 22 super(*args)
20 23 end
4 spec/cpp_project_spec.rb
@@ -77,8 +77,8 @@
77 77 end
78 78
79 79 it 'creates the correct tasks' do
80   - expected_tasks = expected_tasks( [ @project.target ], 'my_namespace' )
81   - missing_tasks = expected_tasks - task_names
  80 + expected = expected_tasks( scoped_tasks( [ @project.target ], 'my_namespace' ), 'my_namespace' )
  81 + missing_tasks = expected - task_names
82 82 missing_tasks.should == []
83 83 end
84 84
138 spec/dependencies_spec.rb
... ... @@ -1,27 +1,141 @@
1 1 load File.dirname(__FILE__) + '/spec_helper.rb'
  2 +require 'fileutils'
2 3
3 4 describe 'the dependencies system' do
4 5
5 6 include RakeBuilderHelper
  7 + include FileUtils
6 8
7 9 before( :each ) do
8 10 Rake::Task.clear
9 11 @project = cpp_task( :executable )
10 12 Rake::Task[ 'clean' ].execute
  13 + rm_f @project.local_config, :verbose => false
11 14 end
12 15
13 16 after( :each ) do
14 17 Rake::Task[ 'clean' ].execute
15 18 end
16 19
17   - it 'says the target is up to date, if nothing changes' do
18   - Rake::Task[ 'build' ].invoke
19   - Rake::Task[ @project.target ].needed?.should_not be_true
  20 + context 'objects_path' do
  21 +
  22 + it 'should not be needed after being called' do
  23 + Rake::Task[ @project.objects_path ].invoke
  24 +
  25 + Rake::Task[ @project.objects_path ].needed?.should be_false
  26 + end
  27 +
20 28 end
21 29
22   - it 'says the build is up to date, if nothing changes' do
23   - Rake::Task[ 'build' ].invoke
24   - Rake::Task[ 'build' ].needed?.should be_false
  30 + context 'missing_headers' do
  31 +
  32 + it 'should not be needed after being invoked' do
  33 + Rake::Task[ 'missing_headers' ].needed?.should be_true
  34 +
  35 + Rake::Task[ 'missing_headers' ].invoke
  36 +
  37 + Rake::Task[ 'missing_headers' ].needed?.should be_false
  38 + end
  39 +
  40 + end
  41 +
  42 + context 'makedepend_file' do
  43 +
  44 + it 'should create the makedepend_file' do
  45 + exist?( @project.makedepend_file ).should be_false
  46 +
  47 + Rake::Task[ @project.makedepend_file ].invoke
  48 +
  49 + exist?( @project.makedepend_file ).should be_true
  50 + end
  51 +
  52 + it 'should not be older than its prerequisites' do
  53 + t = Rake::Task[ @project.makedepend_file ]
  54 +
  55 + t.invoke
  56 +
  57 + stamp = t.timestamp
  58 + t.prerequisites.any? { |n| t.application[n].timestamp > stamp }.should be_false
  59 + end
  60 +
  61 + it 'should have all prerequisites satisfied' do
  62 + t = Rake::Task[ @project.makedepend_file ]
  63 +
  64 + t.invoke
  65 +
  66 + t.prerequisites.any? { |n| t.application[n].needed? }.should be_false
  67 + end
  68 +
  69 + it 'should not say the makedepend_file is needed' do
  70 + t = Rake::Task[ @project.makedepend_file ]
  71 +
  72 + t.needed?.should be_true
  73 +
  74 + t.invoke
  75 +
  76 + t.needed?.should be_false
  77 + end
  78 +
  79 + end
  80 +
  81 + context 'build' do
  82 +
  83 + before :each do
  84 + @task = Rake::Task[ 'build' ]
  85 + end
  86 +
  87 + it 'should have all prerequisites satisfied' do
  88 + @task.invoke
  89 +
  90 + @task.prerequisites.any? { |n| @task.application[n].needed? }.should be_false
  91 + end
  92 +
  93 + it 'should create the makedepend_file' do
  94 + exist?( @project.makedepend_file ).should be_false
  95 +
  96 + Rake::Task[ 'build' ].invoke
  97 +
  98 + exist?( @project.makedepend_file ).should be_true
  99 + end
  100 +
  101 + it 'should create the target' do
  102 + Rake::Task[ 'build' ].invoke
  103 +
  104 + exist?( @project.target ).should be_true
  105 + end
  106 +
  107 + it 'should say the compile task is up to date' do
  108 + Rake::Task[ 'build' ].invoke
  109 +
  110 + Rake::Task[ 'compile' ].needed?.should be_false
  111 + end
  112 +
  113 + it 'should say the build is up to date' do
  114 + Rake::Task[ 'build' ].invoke
  115 +
  116 + Rake::Task[ 'build' ].needed?.should be_false
  117 + end
  118 +
  119 + it 'should say the makedepend_file is up to date' do
  120 + exist?( @project.makedepend_file ).should be_false
  121 +
  122 + isolating_seconds do
  123 + Rake::Task[ 'build' ].invoke
  124 + end
  125 +
  126 + Rake::Task[ @project.makedepend_file ].needed?.should be_false
  127 + end
  128 +
  129 + it 'should say the target is up to date' do
  130 + Rake::Task[ 'build' ].invoke
  131 +
  132 + target_mtime = File.stat( @project.target ).mtime
  133 + @project.target_prerequisites.each do | prerequisite |
  134 + File.stat( prerequisite ).mtime.should be < target_mtime
  135 + end
  136 + Rake::Task[ @project.target ].needed?.should be_false
  137 + end
  138 +
25 139 end
26 140
27 141 it 'doesn\'t recompile objects, if nothing changes' do
@@ -47,7 +161,7 @@
47 161 end
48 162 end
49 163
50   - it 'recompiles source files, if header dependencies' do
  164 + it 'recompiles source files, if header dependencies are more recent' do
51 165 header_file_path = Rake::Path.expand_with_root( 'cpp_project/main.h', SPEC_PATH )
52 166 object_file_path = Rake::Path.expand_with_root( 'main.o', SPEC_PATH )
53 167 isolating_seconds do
@@ -82,11 +196,13 @@
82 196 Rake::Task[ @project.target ].prerequisites.include?( @project.rakefile ).should be_true
83 197 end
84 198
85   - # In our case this spec file is the spec_helper
86   - # i.e., the file that calls Rake::Builder.new
87 199 it 'should indicate the target is out of date, if the Rakefile is newer' do
88 200 Rake::Task[ 'build' ].invoke
  201 +
89 202 Rake::Task[ @project.target ].needed?.should be_false
  203 +
  204 + Rake::Task.clear
  205 + @project = cpp_task( :executable )
90 206 touching_temporarily( @project.target, File.mtime( @project.rakefile ) - 1 ) do
91 207 Rake::Task[ @project.target ].needed?.should be_true
92 208 end
@@ -94,7 +210,11 @@
94 210
95 211 it 'should indicate that a build is needed if the Rakefile changes' do
96 212 Rake::Task[ 'build' ].invoke
  213 +
97 214 Rake::Task[ 'build' ].needed?.should be_false
  215 +
  216 + Rake::Task.clear
  217 + @project = cpp_task( :executable )
98 218 touching_temporarily( @project.target, File.mtime( @project.rakefile ) - 1 ) do
99 219 Rake::Task[ 'build' ].needed?.should be_true
100 220 end
48 spec/local_config_spec.rb
@@ -10,6 +10,9 @@
10 10 @local_config_file = Rake::Path.expand_with_root( '.rake-builder', LOCAL_CONFIG_SPEC_PATH )
11 11 @expected_path = "/some/special/path"
12 12 @config = {:rake_builder=>{:config_file=>{:version=>"1.0"}}, :include_paths=>[ @expected_path ]}
  13 + end
  14 +
  15 + after( :each ) do
13 16 `rm -f '#{ @local_config_file }'`
14 17 end
15 18
@@ -36,6 +39,51 @@
36 39 end.should raise_error( Rake::Builder::BuilderError, 'Config file version missing' )
37 40 end
38 41
  42 + context 'dependencies' do
  43 +
  44 + before( :each ) do
  45 + Rake::Task.clear
  46 + @project = cpp_task( :executable )
  47 + Rake::Task[ 'clean' ].execute
  48 + `rm -f #{ @project.local_config }`
  49 + end
  50 +
  51 + context 'when local_config is invoked' do
  52 +
  53 + it 'should no longer be needed' do
  54 + exist?( @project.local_config ).should be_false
  55 + Rake::Task[ @project.local_config ].needed?.should be_true
  56 +
  57 + Rake::Task[ @project.local_config ].invoke
  58 +
  59 + exist?( @project.local_config ).should be_true
  60 + Rake::Task[ @project.local_config ].needed?.should be_false
  61 + end
  62 +
  63 + end
  64 +
  65 + context 'when load_local_config is invoked' do
  66 +
  67 + it 'should no longer be needed' do
  68 + Rake::Task[ 'load_local_config' ].needed?.should be_true
  69 +
  70 + Rake::Task[ 'load_local_config' ].invoke
  71 +
  72 + Rake::Task[ 'load_local_config' ].needed?.should be_false
  73 + end
  74 +
  75 + it 'local_config should no longer be needed' do
  76 + Rake::Task[ @project.local_config ].needed?.should be_true
  77 +
  78 + Rake::Task[ 'load_local_config' ].invoke
  79 +
  80 + Rake::Task[ @project.local_config ].needed?.should be_false
  81 + end
  82 +
  83 + end
  84 +
  85 + end
  86 +
39 87 private
40 88
41 89 def save_config( config = @config, filename = @local_config_file )
32 spec/microsecond_task_spec.rb
... ... @@ -0,0 +1,32 @@
  1 +load File.dirname(__FILE__) + '/spec_helper.rb'
  2 +require 'fileutils'
  3 +
  4 +describe Rake::Microsecond::DirectoryTask do
  5 +
  6 + include RakeBuilderHelper
  7 + include FileUtils
  8 +
  9 + before :all do
  10 + @path = File.join(File.dirname(__FILE__), 'microsecond_directory')
  11 + end
  12 +
  13 + before :each do
  14 + rm_rf @path, :verbose => false
  15 + end
  16 +
  17 + it 'should memorize the directory creation time including fractional seconds' do
  18 + File.directory?( @path ).should be_false
  19 +
  20 + t = Rake::Microsecond::DirectoryTask.define_task( @path )
  21 +
  22 + isolating_seconds do
  23 + sleep 0.01
  24 + t.invoke
  25 + end
  26 +
  27 + File.directory?( @path ).should be_true
  28 + t.timestamp.usec.should_not == 0
  29 + end
  30 +
  31 +end
  32 +

0 comments on commit 31a9f21

Please sign in to comment.
Something went wrong with that request. Please try again.