I'm trying to expose variables used in my rake task to its test file.
app/lib/tasks/my_rake.rake
module MyRake
VAR = 'foo'
end
task :my_rake do
# use VAR
end
spec/lib/tasks/my_rake_spec.rb
describe 'my_rake' do
include MyRake
# use VAR
end
but when I run my_rake_spec.rb, I get NameError: uninitialized constant MyRake
found this worked:
describe 'my_rake' do
MyRake::VAR
end
Related
I want to call a function that is in another rake file.
Rake File 1:
task :build => [:some_other_tasks] do
foo
end
def foo(type = :debug)
# ...
end
Rake File 2:
require_relative 'path_to_rake_file_1'
task :foo2 => [:some_other_tasks] do
foo
end
I am currently getting a no such file to load error despite confirming the path is absolutely correct.
Instead of defining methods inside rake files and sharing them among rake tasks, it is best practice to create a RakeHelper module and include it in your rake file. So, you could have something like:
rake_helper.rb
module RakeHelper
def self.foo
end
end
task1.rake
include RakeHelper
task :build => [:some_other_tasks] do
RakeHelper.foo
end
task2.rake
include RakeHelper
task :foo2 => [:some_other_tasks] do
RakeHelper.foo
end
I need to:
Open a Rakefile
Find if a certain task is defined
Find if a certain variable is defined
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.
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"
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]
So my deploy.rb script in capistrano starts like this, which I guess is pretty normal:
require 'capistrano/ext/multistage'
require 'nokogiri'
require 'curb'
require 'json'
# override capistrano defaults
set :use_sudo, false
set :normalize_asset_timestamps, false
# some constant of mine
set :my_constant, "foo_bar"
Later, I can access my constant in functions or tasks within namespaces, like:
namespace :mycompany do
def some_function()
run "some_command #{my_constant}"
end
desc <<-DESC
some task description
DESC
task :some_task do
run "some_command #{my_constant}"
end
end
However, if I use the constant in a class, like this:
namespace :mycompany do
class SomeClass
def self.some_static_method()
run "some_command #{my_constant}"
end
end
end
It fails with:
/config/deploy.rb:120:in `some_static_method': undefined local variable or method `my_constant' for #<Class:0x000000026234f8>::SomeClass (NameError)
What am I doing wrong??
Thanks
The deploy.rb file is instance_evaled, this means it's being executed inside the context of an object, and as such anything you declare will be available until you leave that context. As soon as you create a class that provides a new context.
In order to access the original context you have to pass the object (self) to the class method.
namespace :mycompany do
class SomeClass
def self.some_static_method(cap)
run "some_command #{cap.fetch(:my_constant)}"
end
end
SomeClass.some_static_method(self)
end
Although I really don't understand why you are declaring a class like this, it's an odd place for it.
Let's say in my standard deploy.rb file I have a set of namespaces. I have a common task that lists RPM packages based on a variable I pass to it. When I run this as is, it complains about capture being an undefined method. If I include that method inside the deploy.rb file, it works just fine.
Mind you, I'm new to ruby and to OOP so I'm sure I'm doing this the wrong way. :-)
deploy.rb
load 'config/module'
namespace :lp_app do
desc "LP tasks"
co = Tasks::Commands.new()
task :list do
co.list_pkg("LP")
end
end
module.rb
module Tasks
class Commands
def list_pkg(component)
File.open("#{component}.file.list", "r").each_line do |line|
pkg_name = "#{line}".chomp
set :server_pkg, capture("rpm -q #{pkg_name}")
puts "#{server_pkg}"
end
end
end
end
You are trying to use Capistano specific commands outside of Capistrano. If you want to set a variable to the result of something you run on the command line, try the backtick (`).
module Tasks
class Commands
def list_pkg(component)
File.open("#{component}.file.list", "r").each_line do |line|
pkg_name = "#{line}".chomp
server_pkg = `rpm -q #{pkg_name}`
puts server_pkg
end
end
end
end
I am calling a method in a module from another module and am getting a weird error.
require 'nmap'
...
module Enumeration::Hostnames
def reverse_dns ip_addrs
...
ip_addrs.each do |ip_addr|
list = ListScan.test ip_addr #this is the problem
...
end
...
ListScan is in the nmap file.
module ListScan
def ListScan.test target
target = '-sL ' + target
ListScan::parse_results Nmap::Parser.parsescan('nmap',target)
end
...
end
The error is `const_missing': uninitialized constant Enumeration::Hostnames::ListScan (NameError) on the line ListScan.test ip_addr.
Why is it assuming that ListScan is in the Enumeration::Hostnames module? Mixing in ListScan to Hostnames didn't work.
Ruby searches for constants starting from the current context, which in this case is Enumeration::Hostnames.
Try using
::ListScan.test ip_address