From: patrick-may@... (Patrick May) Date: 2002-09-16T14:48:32+09:00 Subject: a few build script recipes for the list 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