I just worked on some build scripts. I thought I found some neat
solutions, and I wanted to share them.
I don’t know if this is really enough for a project. If there is
interest, I’ll rewrite the code with unit tests, address cross
platforms more elegantly, and in general make it a proper library.
~ Patrick
So here goes:
— Targets —
I wrote a Target class that lets you declare new Targets:
Target.new( "name", "long description used for help text" ) {
# ... do something here
}
It keeps track of its instances, so at the end of the build script you
have a bit that does:
if (__FILE__ == $0 )
Target.run( ARGV[0] )
end
It will automatically build the help text, so if it can’t find a given
target it will output something like:
% ruby build.rb
Usage: ruby build.rb [ doc | install | install_depends | test ]
Options: doc -- build rdocs
install -- install narf in site ruby
install_depends -- install dependencies into site_ruby
test -- run narf unit tests
%
To run a particular target, you would do:
% ruby build.rb test
Target: test | run narf unit tests
........................................................
........................................................
...................
124 runs, 218 assertions, 0 failures, 0 errors
%
— Dependencies —
The other bit I wrote was a Lib class to help me install dependencies.
You declare each of your dependencies with a package to check, a url
to use to obtain the package, and the commands to install it:
Lib.new('test/unit',
'http://www.talbott.ws/testunit/packages/testunit-0.1.4.tar.gz'){
['config', 'setup', 'install'].each{ |target|
system("ruby setup.rb #{ target }")
}
}
To show the status of dependencies, you can do
Lib.check
which outputs something like:
% ruby build.rb depends
Target: depends | check dependencies
Checking for test/unit -- yes.
Checking for html-parser -- yes.
Checking for html/tree -- yes.
Checking for strscan -- yes.
%
Then, to install the libraries you:
Lib.install
This will download each tar.gz, unpack it, Dir.chdir into the new
directory, run the install script, cd back out, and delete the archive
and the directory.
— Ugly Things About The Code —
- There are no unit tests (won’t be a good base for long term project)
- On narf, I’m including the cygwin dll and the cygwin rm and tar
utils. This functionality might be able to be implemented in pure
ruby, which could be cleaner - It sure would be nice if Target.run passed arguments to the running
command, wouldn’t it?
— No More Excuses, Here’s The Code —
class Target
@@targets = {}
def Target.run(name="")
if (keys.include? name)
@@targets[name].run
else
help_text
end
end
def Target.keys
@@targets.keys.sort
end
def Target.first
keys.first
end
def Target.justify
justify = 0
keys.each { |key|
justify = key.length if (key.length > justify)
}
justify + 1
end
def Target.help_text
text = "Usage: ruby build.rb [ #{ keys.join( \" | \") } ]" + $/
text += @@targets[keys.first].help_text( "Options: ", justify )
keys.slice(1...keys.length).each{ |key|
text += @@targets[key].help_text( " ", justify )
}
puts text
end
attr_reader :name, :description
def initialize( name, description, &run )
@name = name
@description = description
@run = run || Target.method(:help_text)
@@targets[name] = self
end
def run
$stderr.puts "Target: " + name + " | " + description
@run.call
end
def help_text( prefix, justify )
prefix + name.ljust(justify) + " -- " + description + $/
end
end
class Lib
def Lib.install
@@dependencies.each{ |lib|
lib.install
}
end
def Lib.check
@@dependencies.each{ |lib|
lib.check
}
end
@@dependencies = []
def initialize( package, location, dirname=nil, &install_script )
@package = package
@location = location
@dirname = dirname
@install_script = install_script || proc {}
@@dependencies.push self
end
def installed?
lib = @package.dup
lib += ".rb" unless lib =~ /\.rb\Z/
begin
require lib
rescue LoadError
end
$".include? lib
end
def check
output = ["Checking for #{ @package } -- "]
if (installed?)
output << "yes."
else
output << "no." + $/
output << " You can get it from #{ @location }"
end
output << $/
print output
end
def uri
$: << Lib.dir
require 'uri'
URI::create(@location)
end
def filename
Lib.dir + File.basename(uri.path)
end
def dirname
if (@dirname)
Lib.dir + @dirname
else
filename.sub(/((\.tar\.gz)||(\.tgz))\Z/, "")
end
end
def path_and_query
uri.path + if (uri.query) then "?" + uri.query else "" end
end
def install
unless( installed? )
download
extract
Dir.chdir( dirname )
@install_script.call
Dir.chdir( "../.." )
clean
puts "Installed #{ @package }"
end
end
def download
require "net/http"
puts( "Downloading #{ @package }...",
" contacting #{ @location.slice(0..50) }..." )
open( filename, "wb" ){ |local_file|
Net::HTTP.new( uri.host, 80 ).get( path_and_query ) { |download|
local_file.write download
}
}
puts " ...download saved."
end
def extract
system(bin + "tar -xzf #{ filename } -C lib")
end
def clean
system(bin + "rm -Rf #{ dirname }*" )
end
def bin
if /mswin32/ =~ RUBY_PLATFORM
Lib.dir
else
""
end
end
def Lib.dir
"scripts/"
end
end