如何在Rake任务中运行另一个Rake任务?

438
我有一个Rakefile,它根据全局变量$build_type以两种方式编译项目,$build_type可以是:debug:release(结果放在不同的目录中):
task :build => [:some_other_tasks] do
end

我希望创建一个任务,轮流使用两个配置编译项目,类似于以下内容:
task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    # call task :build with all the tasks it depends on (?)
  end
end

有没有一种方法可以像调用方法一样调用任务?或者如何实现类似的功能?

我会选择社区投票并选择在写作时获得221个赞的答案。原帖发布者已经离开了SO。 - MPritchard
1
正确答案是https://dev59.com/1XRB5IYBdhLWcg3wl4Kc#1290119。 - Blair Anderson
使用类似 Rake::Task["build"].invoke 的方式比使用 system rake build 要更高效,因为它不需要创建新的线程和加载Rails环境,而system rake build 需要这样做。 - Joshua Pinter
7个回答

669
如果您需要任务像方法一样运行,为什么不使用实际的方法呢?
task :build => [:some_other_tasks] do
  build
end

task :build_all do
  [:debug, :release].each { |t| build t }
end

def build(type = :debug)
  # ...
end

如果你更喜欢遵循rake的惯例,以下是从过去的答案中编译出来的可能性:

  • This always executes the task, but it doesn't execute its dependencies:

    Rake::Task["build"].execute
    
  • This one executes the dependencies, but it only executes the task if it has not already been invoked:

    Rake::Task["build"].invoke
    
  • This first resets the task's already_invoked state, allowing the task to then be executed again, dependencies and all:

    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
    
  • Note that dependencies already invoked are not automatically re-executed unless they are re-enabled. In Rake >= 10.3.2, you can use the following to re-enable those as well:

    Rake::Task["build"].all_prerequisite_tasks.each(&:reenable)
    

101
请注意,如果您的任务处于命名空间中,则在调用该任务时必须包括命名空间。例如:Rake::Task['db:reset'].invoke - David Tuite
138
如果任务需要参数,你可以将它们作为参数传递给 #invoke。例如:Rake::Task['with:args'].invoke("pizza") - Trotter
28
如果需要设置环境变量,请在调用invoke之前进行设置。例如:ENV['VERSION'] = '20110408170816'; Rake::Task['db:migrate'].invoke 更多解释请参见这里 - Michael Stalker
16
最近我发现#reenable()并不能重新启用先决条件,而我需要它。Rake(>= 10.3.2)中的此补充,#all_prerequisite_tasks()将遍历所有任务,包括先决条件的先决条件。所以,Rake::Task[task].all_prerequisite_tasks.each &:reenable - Richard Michael
4
@kch,你能把它们串起来吗(例如像命令行中的 rake db:reset db:migrate)?你能做这样的事情吗:Rake::Task["db:reset", "db:migrate"].invoke - Jeff
1
这两个语句不是相互矛盾吗?“首先重置任务的already_invoked状态,使任务可以再次执行,包括所有依赖项[...]请注意,已经调用的依赖项不会被重新执行。”如果一个任务已经被调用,那么它的所有依赖项都必须已经被调用了,对吧?因此,仅仅重新启用并调用将永远不会导致任何任务的依赖项被重新运行,除非您首先重新启用这些任务。 - Ajedi32

131
例如:
Rake::Task["db:migrate"].invoke

7
只有在任务没有被调用时,才会调用此任务。但我需要将它和它所依赖的所有其他任务一起调用两次。 - Arry

61
task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

那应该可以解决你的问题,我自己也需要同样的东西。

这个是可行的,但太啰嗦了。你确定没有更好的方法吗? - kch

20
task :invoke_another_task do
  # some code
  Rake::Task["another:task"].invoke
end

我需要像这样的解决方案的原因之一是,rake任务的加载需要很长时间。通过实现像上面那样的解决方案,会节省加载时间吗? - Dipan Mehta

14
task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    Rake::Task["build"].execute
  end
end

它不起作用,因为它只执行:build任务的主体,并没有调用依赖于它的任务。 - Arry

4
如果您希望每个任务都能运行而不受任何失败的影响,可以这样做:
task :build_all do
  [:debug, :release].each do |t| 
    ts = 0
    begin  
      Rake::Task["build"].invoke(t)
    rescue
      ts = 1
      next
    ensure
      Rake::Task["build"].reenable # If you need to reenable
    end
    return ts # Return exit code 1 if any failed, 0 if all success
  end
end

-1
我建议,如果项目确实需要编译并生成文件,则不要创建通用的调试和发布任务。你应该使用文件任务,在你的例子中这是可行的,因为你已经说明了输出结果会进入不同的目录。 比如说,如果你的项目只是使用gcc将test.c文件编译成out/debug/test.out和out/release/test.out,那么你可以按照以下方式设置你的项目:
WAYS = ['debug', 'release']
FLAGS = {}
FLAGS['debug'] = '-g'
FLAGS['release'] = '-O'
def out_dir(way)
  File.join('out', way)
end
def out_file(way)
  File.join(out_dir(way), 'test.out')
end
WAYS.each do |way|
  desc "create output directory for #{way}"
  directory out_dir(way)

  desc "build in the #{way}-way"
  file out_file(way) => [out_dir(way), 'test.c'] do |t|
    sh "gcc #{FLAGS[way]} -c test.c -o #{t.name}"
  end
end
desc 'build all ways'
task :all => WAYS.map{|way|out_file(way)}

task :default => [:all]

这个设置可以像这样使用:

rake all # (builds debug and release)
rake debug # (builds only debug)
rake release # (builds only release)

这里提供了更多的功能,但仍然满足我的要求:

  1. 如有必要,将创建输出目录。
  2. 只有在需要重新编译时才会重新编译文件(此示例仅适用于最简单的test.c文件)。
  3. 如果您想触发发布构建或调试构建,则可以轻松获得所有任务。
  4. 此示例还包括一种定义调试和发布构建之间小差异的方式。
  5. 无需重新启用使用全局变量参数化的构建任务,因为现在不同的构建具有不同的任务。构建任务的代码重用是通过重用代码来定义构建任务来完成的。请注意,循环不会执行相同的任务,而是创建可以稍后触发的任务(可以通过all-task或在rake命令行上选择其中之一来触发它们)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接