Reputation: 39243
I need to:
This works to find tasks defined inside a Rakefile, but it pollutes the global namespace (i.e. if you run it twice, all tasks defined in first one will show up in the second one):
sub_rake = Rake::DefaultLoader.new
sub_rake.load("Rakefile")
puts Rake.application.tasks
In Rake, here is where it loads the Makefile:
https://github.com/ruby/rake/blob/master/lib/rake/rake_module.rb#L28
How do I get access to the variables that are loaded there?
Here is an example Rakefile I am parsing:
load '../common.rake'
@source_dir = 'source'
desc "Run all build and deployment tasks, for continuous delivery"
task :deliver => ['git:pull', 'jekyll:build', 'rsync:push']
Here's some things I tried that didn't work. Using eval
on the Rakefile:
safe_object = Object.new
safe_object.instance_eval("Dir.chdir('" + f + "')\n" + File.read(folder_rakefile))
if safe_object.instance_variable_defined?("@staging_dir")
puts " Staging directory is " + f.yellow + safe_object.instance_variable_get("@staging_dir").yellow
else
puts " Staging directory is not specified".red
end
This failed when parsing desc
parts of the Rakefile. I also tried things like
puts Rake.instance_variables
puts Rake.class_variables
But these are not getting the @source_dir
that I am looking for.
Upvotes: 0
Views: 516
Reputation: 39243
Rake runs load()
on the Rakefile inside load_rakefile
in the Rake
module. And you can easily get the tasks with the public API.
Rake.load_rakefile("Rakefile")
puts Rake.application.tasks
Apparently that load()
invocation causes the loaded variables to be captured into the main
Object
. This is the top-level Object
of Ruby. (I expected it to be captured into Rake
since the load
call is made in the context of the Rake
module.)
Therefore, it is possible to access instance variables from the main
object using this ugly code:
main = eval 'self', TOPLEVEL_BINDING
puts main.instance_variable_get('@staging_dir')
Here is a way to encapsulate the parsing of the Rakefile so that opening two files will not have all the things from the first one show up when you are analyzing the second one:
class RakeBrowser
attr_reader :tasks
attr_reader :variables
include Rake::DSL
def task(*args, &block)
if args.first.respond_to?(:id2name)
@tasks << args.first.id2name
elsif args.first.keys.first.respond_to?(:id2name)
@tasks << args.first.keys.first.id2name
end
end
def initialize(file)
@tasks = []
Dir.chdir(File.dirname(file)) do
eval(File.read(File.basename(file)))
end
@variables = Hash.new
instance_variables.each do |name|
@variables[name] = instance_variable_get(name)
end
end
end
browser = RakeBrowser.new(f + "Rakefile")
puts browser.tasks
puts browser.variables[:@staging_dir]
Upvotes: 0
Reputation: 31726
rakefile_body = <<-RUBY
load '../common.rake'
@source_dir = 'some/source/dir'
desc "Run all build and deployment tasks, for continuous delivery"
task :deliver => ['git:pull', 'jekyll:build', 'rsync:push']
RUBY
def source_dir(ast)
return nil unless ast.kind_of? AST::Node
if ast.type == :ivasgn && ast.children[0] == :@source_dir
rhs = ast.children[1]
if rhs.type != :str
raise "@source_dir is not a string literal! #{rhs.inspect}"
else
return rhs.children[0]
end
end
ast.children.each do |child|
value = source_dir(child)
return value if value
end
nil
end
require 'parser/ruby22'
body = Parser::Ruby22.parse(rakefile_body)
source_dir body # => "some/source/dir"
Upvotes: 1