Difference between order_only prerequisites and non-order_only prerequisites in rake

Viewed 131

Rake has added an order_only feature in 13.0, which is probably based on GNU Make's order-only prerequisites.

Information on how order-only prerequisites should work is further clarified in this question.

The problem however is I don't see how it actually works in rake. Like for example if I run rake install and then I run rake build, rake install doesn't run again when build is not an order only prerequisite of it. Maybe I'm just misinformed, or maybe some other factors like timestamps and how the needed? method affect how it behaves, but I'm a little tired to further examine the source code of rake.

In the merge commit, order only prerequisites seem only assigned differently from prerequisites, and nothing else is demonstrated.

Anyway I hope someone can elaborately describe how order_only should work in rake with completeness; showing examples and citing source code of rake if possible. This will greatly help me and other Rakefile writers.

1 Answers

It looks to me like someone added this so that you could build some order_only commands in Rake, but didn't add support for this to the built-in file commands. It does store the order_only prerequisites in a separate member variable in the task, so you can make your own version of the FileTask that will work like the GNU Make's order-only prerequisites. Here's an example Rakefile:

class OrderOnlyFileTask < Rake::FileTask
  def out_of_date?(stamp)
    all_regular_prerequisite_tasks.any? { |prereq|
      prereq_task = application[prereq, @scope]
      if prereq_task.instance_of?(Rake::FileTask)
        prereq_task.timestamp > stamp || @application.options.build_all
      else
        prereq_task.timestamp > stamp
      end
    }
  end
  
  def all_regular_prerequisite_tasks
    seen = {}
    collect_regular_prerequisites(seen)
    seen.values
  end
  
  def collect_regular_prerequisites(seen)
    regular_prerequisite_tasks.each do |pre|
      next if seen[pre.name]
      seen[pre.name] = pre
      pre.collect_prerequisites(seen)
    end
  end
  
  def regular_prerequisite_tasks
    prerequisites.map { |pre| lookup_prerequisite(pre) }
  end
end   

file 'foo/created.txt' do
  mkdir 'foo'
  touch 'foo/created.txt'
end

file 'source.txt' do
  touch 'source.txt'
end

OrderOnlyFileTask.define_task('foo/built.txt' => ['source.txt'], order_only: ['foo/created.txt']) do
  touch 'foo/built.txt'
end

task :build => 'foo/built.txt' do
  puts 'build'
end

If I start from scratch and run rake build I get this output:

% rake build
touch source.txt
mkdir foo
touch foo/created.txt
touch foo/built.txt
build

If I run it again:

% rake build
build

If I update the mtime on foo/created.txt:

% touch foo/created.txt
% rake build
build

If I modify the source file:

% touch source.txt
% rake build
touch foo/built.txt
build

If I delete the foo directory:

% rm -rf foo
% rake build
mkdir foo
touch foo/created.txt
touch foo/built.txt
build

If you change the OrderOnlyFileTask to have a regular prerequisite for foo/created.txt like this:

OrderOnlyFileTask.define_task('foo/built.txt' => ['source.txt', 'foo/created.txt']) do
  touch 'foo/built.txt'
end

Then you get different behavior when foo/created.txt is touched:

% touch foo/created.txt
% rake build
touch foo/built.txt
build

I made the dependency here a file (foo/created.txt) instead of a directory like in the GNU Make example because the directory task in Rake doesn't check the mtime of the directory like directory dependencies in make.

So with the order_only option to a task you can build your own Rake tasks that treat the order_only prerequisites differently when deciding whether to execute or not. You just need to define needed? in your task to handle prerequisites how you want. All those methods I put in OrderOnlyFileTask are to change the needed? method in FileTask to ignore the order_only prerequisites.

Related