如何在 Rake 的不同目录中处理输入和输出?

How To Process Inputs and Outputs in Separate Directories in Rake?

我正在尝试使用 Rake 将 Haml 文件转换为 HTML 文件,这样如果 Haml 文件没有更改,我就不会重新生成现有的 HTML.

但是,我不希望我的输入文件、输出文件和 Rakefile 全部混入一个目录中。相反,我希望我的输入文件位于 src/ 中,而我的输出文件位于 build/output/ 中。所以,我想开始:

Rakefile
src/
  slides.haml

我想结束:

Rakefile
src/
  slides.haml
build/
  output/
    slides.html

我已经尝试了几位 Rake 代码,但没有成功。

首先,我尝试了穴居人方法,对我正在尝试的精确文件进行硬编码:

task "build/output/slides.html" => "src/slides.haml" do
  touch task.name
end

task :slides => "src/slides.haml"
task :default => "slides"

运行 rake --trace 结果:

** Invoke default (first_time)
** Invoke slides (first_time)
** Invoke src/slides.haml (first_time, not_needed)
** Execute slides
** Execute default

目录没有建立,我没有空的build/output/slides.html文件。

理想情况下,我更喜欢 rule-和-FileList 方法,使用 this SO answer or this "Rake cheatshet" entry 之类的东西。但是,我尝试了该主题的一些变体,得到了相同的结果,这表明我搞砸了一些更深层次的事情。我已将我的 Rakefile 调整回穴居人方法,只是想弄清楚这里发生了什么。

为什么 Rake 无法识别我的 "build/output/slides.html" 任务?在 Rake 中使用子目录(输入或输出)有什么神奇之处吗?

首先,任务声明为task :name => dependency。您的 :slides 任务命名了一个源而不是输出依赖项。此外,您的 :default 任务依赖于文件 "slides" 而不是任务 :slides。将这些行更改为

task :slides => "build/output/slides.html"
task :default => :slides

接下来,实际执行的代码块应该是规则,而不是任务,您可以通过#{t.<param>}:

访问规则的内部信息
rule "build/output/slides.html" => "src/slides.html" do |t|
  sh "mkdir -p $(dirname #{t.name})"
  sh "touch #{t.name}"
end

有更好的方法可以使规则更通用,构建源文件列表并将它们映射到输出等。这里有一些有用的教程 https://github.com/ruby/rake#presentations-and-articles-about-rake

如果 build/output/ 目录不存在,可能还有更多 Ruby 风格的方法,而不是调用 shell 来创建它。

我错过了文件任务使用 file 而不是 task,并且还有一个 directory 任务。

穴居人方法的工作版本是:

namespace :build do
  directory "build/output"

  file "build/output/slides.html" => ["build/output", "src/slides.haml"] do |t|
    touch t.name
  end

  task :all => "build/output/slides.html"
end

task :default => ['build:all']

这是我今天放在一起的一些东西,我相信它可以完成你想要完成的同样的事情。首先,我定义mv_rule(可以在你import/require的单独ruby文件中),然后为输出目录定义一个directory任务,最后做一个描述输出模式(包括目录)和输入模式(包括目录)的规则。

# Declare a rule for auto-tasks that have input and output in different directories
#
# Example:
#  mv_rule 'obj/*.o' => 'src/*.c' do |t|
#    sh "gcc -c #{t.source} -o #{t.name}"
#
def mv_rule(*args, &block) # :doc:
  outFile, inFile = args[0].keys[0], args[0].values[0]
  outExt = File.extname outFile
  outDir = File.dirname outFile
  inExt = File.extname inFile
  inDir = File.dirname inFile
  destPattern = Regexp.new("#{outDir}/.*\#{outExt}")
  srcPattern = ->(o){o.pathmap("%{^#{outDir}/,#{inDir}/}X#{inExt}")}
  Rake::Task.create_rule(destPattern => [srcPattern, outDir], &block)
end

directory "build/output"

mv_rule 'build/output/*.html' => 'src/*.haml' do |t|
    touch t.name
end

task :all => "build/output/slides.html"

task :default => [:all]

我可以 运行 它与 rake 一起执行 :default 任务。或者,由于我在适当的目录中指定了从 haml 制作 html 的一般规则,我可以 运行 它与 rake build/output/more_slides.html (假设有一个 src/more_slides.haml) .

我有点惊讶没有 built-in 方法来指定不同的输出目录。这似乎是一个很常见的场景。

我的 mv_rule 是为了用漂亮的 straight-forward 语法来解决问题。它可能有我什至没有想过的局限性。我使用 http://www.virtuouscode.com/2014/04/23/rake-part-3-rules/ and http://www.virtuouscode.com/2014/04/24/rake-part-4-pathmap/ 作为灵感。

这项工作的非穴居人工具是 pathmap

SOURCES = FileList['src/*.haml']

directory 'build/output'

rule '.html' do |t|
  sh 'touch', t.name
end

task :default => ['build/output', SOURCES.pathmap('build/output/%n.html')]

输出:

$ mkdir src && touch src/slides.haml
$ rake --trace
** Invoke default (first_time)
** Invoke build/output (first_time)
** Execute build/output
mkdir -p build/output
** Invoke build/output/slides.html (first_time)
** Execute build/output/slides.html
touch build/output/slides.html
** Execute default

对于此类任务,您可能还对 rake/clean 生成任务以将项目恢复到原始状态感兴趣:

require 'rake/clean'
CLOBBER.include 'build' # running 'rake clobber' will remove the build directory