Rake, how to create a task with multiple inputs and one output? - ruby

We have a rails app, and we use webpack which takes multiple javascript files and outputs one single javascript file. It takes a long time to run, and I'd like to create a rake task for this. But being new to rake I need some help.
I'd like to use rake's build system so that I can get automatic checking of the time stamps between the input and output .js files. So that if any of the input files are newer than the output file it will execute webpack. Otherwise if the none of the input files are newer than the output file, than the task does nothing.
In MSBuild, this is a cakewalk and lightning fast. But in Ruby I'm kind of lost.
I'm guessing it might consist of writing file tasks, and looping through and making the one output file depend on the inputs. Or should I use a rule, like this?
outputfile = "~/foo.js"
inputfiles = Dir["~/**/*.js"]
rule outputfile => inputfiles do
bin/webpack bla bla bla
end

You can use Rake::FileList to achieve this. Something like this:
file "foo.js" => Rake::FileList["**/*.js"] do
...
end
And, I'm not sure whether rake allows to use ~ in paths, I believe a full path is required. Or just use a "#{Dir.home}/foo.js" rule.
Then call it using:
rake ~/foo.js
And when you have multiple outputs:
task :build => Rake::FileList["config1.xml", "config2.xml"] do
# all that stuff is run only when the FileList above is changed
touch 'foo1.js'
touch 'foo2.js'
sh "compile foo3.js"
sh "do-anything-else foo4.js"
end
Run it using:
rake build

Related

Rake task selectivly ignoring new code

In a rake task I'm writing some puts statements show changes while others don't. For instance changing
puts model+" | "+id
into
puts model+" * "+id
doesn't change in the output of the script. However in some places changing
puts "Connecting to "+site
into
puts "Connecting to ----"+site
shows the changes that where made.
In the places where any changes to the line doesn't change the output, adding a new puts statement before or after don't show up when the task is run. Commenting out lines of code around the unchanging puts statements that do the actual work cause the script to not execute those lines, just as it should, but changing or adding puts statements there do not change the output of the script.
Removing all other tasks and emacs backup files from the lib/tasks folder doesn't help. I've been bitten before by having a backup copy of a task with the same namespace and task name running instead of the one I was working on.
This is being run with Ruby 2.4.3 on OpenBSD 6.3-stable on a fx-8350. I would post the whole script but the company I'm working for won't allow it.
How about
puts "#{model} +/*/whatever #{site}"
It shouldn't matter to what sounds like a filesystem update issue (reboot), but it's probably better form to put the variables in the string like that instead of + "" them.

Combine many ruby source files into a single file

I'm working on a project in ruby, and I have many source files each declaring a few classes and methods in a module. I've been looking around and I may just be missing something, but I can't seem to find a way to merge all of my source files into a single file. I want a single file because the end product here is meant to be a command line tool, and users won't want to install 20 files in order to run a single command.
Is there any way to take many ruby source files and process all of the require statements ahead of time to create a single file that can be run by itself as a stand-alone program?
A few things that may be useful (or harmful?):
The only file with code that is not within a function is the main file. Therefore, only the main file will have code that is run immediately after the file is parsed.
I require all needed files immediately at the start of each file (after the initial comment), so the require statements are all at the top of the file, and all dependancies are listed at the start of each file
I call require on all files required by each file, regardless of weather or not they may have been included already.
Example (A few files from my project):
<filename> --> <include1>
<include2>
...
build.rb [the main file] --> BuildSystem.rb
Utilities.rb
BuildSystem.rb --> Project.rb
YamlFile.rb
XmlFile.rb
Utilities.rb
Project.rb --> YamlFile.rb
XmlFile.rb
Utilities.rb
What I'm looking for would be something that would allow me to combine all 5 of these files into a single build file that can be installed just by putting it in the right place. Any help would be great, thanks!
The only file with code that is not within a function is the main file. Therefore, only the main file will have code that is run immediately after the file is parsed.
Because of this, you may be able to simply run the following from the shell (assuming a OS X / *nix system):
touch main.rb
cat Utilities.rb >> main.rb
cat XmlFile.rb >> main.rb
cat YamlFile.rb >> main.rb
cat Project.rb >> main.rb
cat BuildSystem.rb >> main.rb
cat build.rb >> main.rb # must be appended last
You can put this into a shell script to "build" your output file each time you make a change.
Using this method, you will have require statements scattered throughout the output main.rb file, but since they are idempotent it won't have any negative effects.

How to use FileLists as rake dependencies

I'm using rake to help compile Coffeescript for a Chrome extension I'm writing.
My Rakefile looks like this:
COFFEE = FileList['src/*.coffee']
JS = COFFEE.ext 'js'
directory 'extension'
rule '.js' => ['.coffee', 'extension'] do |t|
`coffee -c -o extension #{t.source}`
end
desc "Build the extension in the 'extension' directory"
task :build => ['extension', JS] do
cp File.join('src', 'manifest.json'), 'extension'
end
When I only have one .coffee file in my src directory, there's no problem. But as soon as I have more than one .coffee files it errors:
$ rake build
> rake aborted!
> Don't know how to build task 'src/app.js src/background.js'
>
> Tasks: TOP => build
> (See full trace by running task with --trace)
Is it possible to specify a FileList as a dependency? How else would I tell rake that I want all my Coffeescript files compiled durring the build task?
Rake’s dependency list is an Array of task names. When you use a FileList as one of its elements, you nest arrays – effectively, this:
task :build => ['extension', ['src/app.js', 'src/background.js']] do
Rake just uses the String representation of all passed dependency Array elements, which is why it complains about being unable to build a 'src/app.js src/background.js' task (note how this is one string).
Splatting your FileList (or flattening the dependency Array) will solve the issue, i.e.:
task :build => ['extension', *JS] do
or
task :build => ['extension', JS].flatten do
Try this:
files = Dir.entries('path/to/scripts').select { |f| f.include? '.coffee' }
files.each do |file_path|
`coffee -c -o extension #{file_path}`
end
So far in my search it looks like the only way to accomplish what I want is to either have a task which loops through my FileList and compiles each one explicitly (like in the answer from #nicooga). Or, I can loop through everything in the FileList and add it as a dependency to the build task.
I don't like either of these because rake has FileLists for getting groups of files, rules for defining how to handle kinds of files, and a nice syntax for defining dependencies, but apparently no way to combine all three of those together.
So, my solution for now is to go with the second option, adding each file as a dependency. The shortest way I've found to do this is to concat the FileList onto the dependency array. So now my build task looks like this:
task :build => ['extension'].concat(JS) do
cp File.join('src', 'manifest.json'), 'extension'
end
And thanks to the comment by #kopischke, this can even be shortened to ['extension' *JS] using the splat operator.
Late to the game, but a FileList is lazy, and that is useful, especially if you have lots of file matching.
Think of C or C++ where you potentially can have many that require dependencies.
Most of the answers here require the FileList to be expanded/evaluated. Since Rake is Ruby and task is where the last argument is basically a Hash, you will be evaluating the array.
One of the answers suggests ['extension', JS].flatten. This approach will search the directory and collect all the .js files. This approach is fine when you have a task that will always get executed/invoked. However, you don't want to evaluate the FileList and invoke its search if the task is will not get executed/invoked.
The best way to use a FileList is the following
fl = FileList['extension', "src/*.coffee"] do |c|
c.ext('js')
end
file "somefile" => fl do
#some stuff
end
Then if "somefile" doesn't exist, or the "somefile" task is not even invoked, the read of your Rakefile doesn't invoke the FileList expansion, saving a bunch of time.

Rake synthesized tasks and file date checking

I have a Rakefile I'm using to generate HTML from markdown (and do some other stuff that's irrelevant to the question.
I'm generating files from my source, .feature files (in the FileList DOCUMENTS), into my output directory OUTPUT as HTML. I have an htmlfile method to assemble and write my HTML file.
I'm trying two alternative options here:
File tasks:
DOCUMENTS.each do |doc|
file doc.pathmap("#{OUTPUT}/%X.html") => doc do |t|
htmlfile t.name, RDiscount.new(F.read doc).to_html, t.name.pathmap('%n')
end
end
Synthesized file tasks with a rule:
rule '.html' => proc {|html| html.pathmap("%{#{OUTPUT}/,}X.feature")} do |t|
htmlfile t.name, RDiscount.new(F.read t.source).to_html, t.name.pathmap('%n')
end
My understanding was that the latter option would synthesize file tasks, and have the same net effect. However I find that if I choose it, it does not cope with incremental building, whereas the first option does.
If I build, then modify one file, then run rake --trace I get the following:
With synthesized tasks:
** Invoke output/Module/Feature.html (first_time, not_needed)
** Invoke output/Module (not_needed)
And with the explicit file tasks:
** Invoke output/Module/Feature.html (first_time)
** Invoke output/Module (not_needed)
** Invoke Module/Feature.feature (first_time, not_needed)
** Execute output/Module/Feature.html
This option is clearly checking the source file. I thought linking output and source was exactly what rule
(I believe it's most helpful to put the answer as an actual answer, rather than a comment. See https://meta.stackexchange.com/questions/68507/what-to-do-if-you-find-the-answer-to-your-own-question)
It turns out that if you have file outdoc => something elsewhere in your Rakefile, it will mess with synthesized tasks. Whereas if you have file tasks for those output documents it adds to the pre-requisites and works fine. This sort of makes sense; synthesized tasks don't really exist.
I also found out that rules only work to one level of inference ( http://onestepback.org/articles/buildingwithrake/rulelimitations.html) though that didn't turn out to be the answer.
Fix: rearrange pre-requisites of tasks, or use the explicit file tasks.

Redirect Output of Capistrano

I have a Capistrano deploy file (Capfile) that is rather large, contains a few namespaces and generally has a lot of information already in it. My ultimate goal is, using the Tinder gem, paste the output of the entire deployment into Campfire. I have Tinder setup properly already.
I looked into using the Capistrano capture method, but that only works for the first host. Additionally that would be a lot of work to go through and add something like:
output << capture 'foocommand'
Specifically, I am looking to capture the output of any deployment from that file into a variable (in addition to putting it to STDOUT so I can see it), then pass that output in the variable into a function called notify_campfire. Since the notify_campfire function is getting called at the end of a task (every task regardless of the namespace), it should have the task name available to it and the output (which is stored in that output variable). Any thoughts on how to accomplish this would be greatly appreciated.
I recommend not messing with the Capistrano logger, Instead use what unix gives you and use pipes:
cap deploy | my_logger.rb
Where your logger reads STDIN and STDOUT and both records, and pipes it back to the appropriate stream.
For an alternative, the Engineyard cap recipies have a logger – this might be a useful reference if you do need to edit the code, but I recommend not doing.
It's sort of a hackish means of solving your problem, but you could try running the deploy task in a Rake task and capturing the output using %x.
# ...in your Rakefile...
task :deploy_and_notify do
output = %x[ cap deploy ] # Run your deploy task here.
notify_campfire(output)
puts output # Echo the output.
end

Resources