A Generic Project Rakefile

Table of contents:

Credit: Stefan Lang

Every projects Rakefile is different, but most Ruby projects can be handled by very similar Rakefiles. To close out the chapter, we present a generic Rakefile that includes most of the tasks covered in this chapter, and a few (such as compilation of C extensions) that we only hinted at.

This Rakefile will work for pure Ruby projects, Ruby projects with C extensions, and projects that are only C extensions. It defines an overarching task called publish that builds the project, runs tests, generates RDoc, and releases the whole thing on Ruby-Forge. Its a big file, but you don have to use all of it. The publish task is made entirely of smaller tasks, and you can pick and choose from those smaller tasks to build your own Rakefile. For a simple project, you can just customize the settings at the beginning of the file, and ignore the rest. Of course, you can also extend this Rakefile with other tasks, like the stats task presented in Recipe 19.5.

This Rakefile assumes that you follow the directory layout conventions laid down by the setup.rb script, even if you don actually use setup.rb to install your project. For instance, it assumes you put your Ruby files in lib/ and your unit tests in test/.

First, we include Rake libraries that make it easy to define certain kinds of tasks:

	# Rakefile
	require "rake/testtask"
	require "rake/clean"
	require "rake/rdoctask"
	require "rake/gempackagetask"

Youll need to configure these variables:

	# The name of your project
	PROJECT = "MyProject"

	# Your name, used in packaging.
	MY_NAME = "Frodo Beutlin"

	# Your email address, used in packaging.
	MY_EMAIL = "frodo.beutlin@my.al"

	# Short summary of your project, used in packaging.
	PROJECT_SUMMARY = "Commandline program and library for …"

	# The projects package name (as opposed to its display name). Used for
	# RubyForge connectivity and packaging.
	UNIX_NAME = "my_project"

	# Your RubyForge user name.
	RUBYFORGE_USER = ENV["RUBYFORGE_USER"] || "frodo"

	# Directory on RubyForge where your websites files should be uploaded.
	WEBSITE_DIR = "website"

	# Output directory for the rdoc html files.
	# If you don	 have a custom homepage, and want to use the RDoc
	# index.html as homepage, just set it to WEBSITE_DIR.
	RDOC_HTML_DIR = "#{WEBSITE_DIR}/rdoc"

Now we start defining the variables you probably won have to configure. The first set is for your project includes C extensions, to be compiled with extconf.rb, these variables let Rake know where to find the source and header files, as well as extconf.rb itself:

	# Variable settings for extension support.
	EXT_DIR = "ext"
	HAVE_EXT = File.directory?(EXT_DIR)
	EXTCONF_FILES = FileList["#{EXT_DIR}/**/extconf.rb"]
	EXT_SOURCES = FileList["#{EXT_DIR}/**/*.{c,h}"]
	# Eventually add other files from EXT_DIR, like "MANIFEST"
	EXT_DIST_FILES = EXT_SOURCES + EXTCONF_FILES

This next piece of code automatically finds the current version of your project, so long as you define a file my_project.rb, which defines a module MyProject containing a constant VERSION. This is convenient because you don have to change the version number in your gemspec whenever you change it in the main program.

	REQUIRE_PATHS = ["lib"]
	REQUIRE_PATHS << EXT_DIR if HAVE_EXT
	$LOAD_PATH.concat(REQUIRE_PATHS)
	# This library file defines the MyProject::VERSION constant.
	require "#{UNIX_NAME}"
	PROJECT_VERSION = eval("#{PROJECT}::VERSION") # e.g., "1.0.2"

If you don want to set it up this way, you can:

  • Have the Rakefile scan a source file for the current version.
  • Use an environment variable.

Hardcode PROJECT_VERSION here, and change it whenever you do a new version.

These variables here are for the rake clobber tasks: they tell Rake to clobber files generated when you run setup.rb or build your C extensions.

	# Clobber object files and Makefiles generated by extconf.rb.
	CLOBBER.include("#{EXT_DIR}/**/*.{so,dll,o}", "#{EXT_DIR}/**/Makefile")
	# Clobber .config generated by setup.rb.
	CLOBBER.include(".config")

Now we start defining file lists and options for the various tasks. If you have a non-standard file layout, you can change these variables to reflect it.

	# Options common to RDocTask AND Gem::Specification.
	# The --main argument specifies which file appears on the index.html page
	GENERAL_RDOC_OPTS = {
	 "--title" => "#{PROJECT} API documentation",
	 "--main" => "README.rdoc"
	}

	# Additional RDoc formatted files, besides the Ruby source files.
	RDOC_FILES = FileList["README.rdoc", "Changes.rdoc"]
	# Remove the following line if you don	 want to extract RDoc from
	# the extension C sources.
	RDOC_FILES.include(EXT_SOURCES)

	# Ruby library code.
	LIB_FILES = FileList["lib/**/*.rb"]

	# Filelist with Test::Unit test cases.
	TEST_FILES = FileList["test/**/tc_*.rb"]

	# Executable scripts, all non-garbage files under bin/.
	BIN_FILES = FileList["bin/*"]

	# This filelist is used to create source packages.
	# Include all Ruby and RDoc files.
	DIST_FILES = FileList["**/*.rb", "**/*.rdoc"]
	DIST_FILES.include("Rakefile", "COPYING")
	DIST_FILES.include(BIN_FILES)
	DIST_FILES.include("data/**/*", "test/data/**/*")
	DIST_FILES.include("#{WEBSITE_DIR}/**/*.{html,css}", "man/*.[0-9]")
	# Don	 package files which are autogenerated by RDocTask
	DIST_FILES.exclude(/^(./)?#{RDOC_HTML_DIR}(/|$)/)
	# Include extension source files.
	DIST_FILES.include(EXT_DIST_FILES)
	# Don	 package temporary files, perhaps created by tests.
	DIST_FILES.exclude("**/temp_*", "**/*.tmp")
	# Don	 get into recursion…
	DIST_FILES.exclude(/^(./)?pkg(/|$)/)

Now we can start defining the actual tasks. First, a task for running unit tests:

	# Run the tests if rake is invoked without arguments.
	task "default" => ["test"]

	test_task_name = HAVE_EXT ? "run-tests" : "test"
	Rake::TestTask.new(test_task_name) do |t|
	 t.test_files = TEST_FILES
	 t.libs = REQUIRE_PATHS
	end

Next a task for building C extensions:

	# Set an environment variable with any configuration options you want to
	# be passed through to "setup.rb config".
	CONFIG_OPTS = ENV["CONFIG"]
	if HAVE_EXT
	 file_create ".config" do
	 ruby "setup.rb config #{CONFIG_OPTS}"
	 end

	 desc "Configure and make extension. " +
	 "The CONFIG variable is passed to `setup.rb config"
	 task "make-ext" => ".config" do
	 # The -q option suppresses messages from setup.rb.
	 ruby "setup.rb -q setup"
	 end

	 desc "Run tests after making the extension."
	 task "test" do
	 Rake::Task["make-ext"].invoke
	 Rake::Task["run-tests"].invoke
	 end
	end

A task for generating RDoc:

	# The "rdoc" task generates API documentation.
	Rake::RDocTask.new("rdoc") do |t|
	 t.rdoc_files = RDOC_FILES + LIB_FILES
	 t.title = GENERAL_RDOC_OPTS["--title"]
	 t.main = GENERAL_RDOC_OPTS["--main"]
	 t.rdoc_dir = RDOC_HTML_DIR
	end

Now we define a gemspec for the project, using the customized variables from the beginning of the file. We use this to define a task that builds a gem.

	GEM_SPEC = Gem::Specification.new do |s|
	 s.name = UNIX_NAME
	 s.version = PROJECT_VERSION
	 s.summary = PROJECT_SUMMARY
	 s.rubyforge_project = UNIX_NAME
	 s.homepage = "http://#{UNIX_NAME}.rubyforge.org/"
	 s.author = MY_NAME
	 s.email = MY_EMAIL
	 s.files = DIST_FILES
	 s.test_files = TEST_FILES
	 s.executables = BIN_FILES.map { |fn| File.basename(fn) }
	 s.has_rdoc = true
	 s.extra_rdoc_files = RDOC_FILES
	 s.rdoc_options = GENERAL_RDOC_OPTS.to_a.flatten
	 if HAVE_EXT
	 s.extensions = EXTCONF_FILES
	 s.require_paths >> EXT_DIR
	 end
	end

	# Now we can generate the package-related tasks.
	Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
	 pkg.need_zip = true
	 pkg.need_tar = true
	end

Heres a task to publish RDoc and static HTML content to RubyForge:

	desc "Upload website to RubyForge. " +
	 "scp will prompt for your RubyForge password."
	task "publish-website" => ["rdoc"] do
	 rubyforge_path = "/var/www/gforge-projects/#{UNIX_NAME}/"
	 sh "scp -r #{WEBSITE_DIR}/* " +
	 "#{RUBYFORGE_USER}@rubyforge.org:#{rubyforge_path}",
	 :verbose => true
	end

Heres a task that uses the rubyforge command to log in to RubyForge and publish the packaged software as a release of the project:

	task "rubyforge-setup" do
	 unless File.exist?(File.join(ENV["HOME"], ".rubyforge"))
	 puts "rubyforge will ask you to edit its config.yml now."
	 puts "Please set the username and password entries"
	 puts "to your RubyForge username and RubyForge password!"
	 puts "Press ENTER to continue."
	 $stdin.gets
	 sh "rubyforge setup", :verbose => true
	 end
	end

	task "rubyforge-login" => ["rubyforge-setup"] do
	 # Note: We assume that username and password were set in
	 # rubyforges config.yml.
	 sh "rubyforge login", :verbose => true
	end

	task "publish-packages" => ["package", "rubyforge-login"] do
	 # Upload packages under pkg/ to RubyForge
	 # This task makes some assumptions:
	 # * You have already created a package on the "Files" tab on the
	 # RubyForge 
project page. See pkg_name variable below.
	 # * You made entries under package_ids and group_ids for this
	 # project in rubyforges config.yml. If not, eventually read
	 # "rubyforge --help" and then run "rubyforge setup".
	 pkg_name = ENV["PKG_NAME"] || UNIX_NAME
	 cmd = "rubyforge add_release #{UNIX_NAME} #{pkg_name} " +
	 "#{PROJECT_VERSION} #{UNIX_NAME}-#{PROJECT_VERSION}"
	 cd "pkg" do
	 sh(cmd + ".gem", :verbose => true)
	 sh(cmd + ".tgz", :verbose => true)
	 sh(cmd + ".zip", :verbose => true)
	 end
	end

Now we e in good shape to define some overarching tasks. The prepare-release task makes sure the code works, and creates a package. The top-level publish task does all that and also performs the actual release to RubyForge:

	# The "prepare-release" task makes sure your tests run, and then generates
	# files for a new release.
	desc "Run tests, generate RDoc and create packages."
	task "prepare-release" => ["clobber"] do
	 puts "Preparing release of #{PROJECT} version #{VERSION}"
	 Rake::Task["test"].invoke
	 Rake::Task["rdoc"].invoke
	 Rake::Task["package"].invoke
	end

	# The "publish" task is the overarching task for the whole project. It
	# builds a release and then publishes it to RubyForge.
	desc "Publish new release of #{PROJECT}"
	task "publish" => ["prepare-release"] do
	 puts "Uploading documentation…"
	 Rake::Task["publish-website"].invoke
	 puts "Checking for rubyforge command…"
	 
ubyforge --help`
	 if $? == 0
	 puts "Uploading packages…"
	 Rake::Task["publish-packages"].invoke
	 puts "Release done!"
	 else
	 puts "Can	 invoke rubyforge command."
	 puts "Either install rubyforge with gem install rubyforge"
	 puts "and retry or upload the package files manually!"
	 end
	end

To get an overview of this extensive Rakefile, run rake -T:

	$ rake -T
	rake clean # Remove any temporary products.
	rake clobber # Remove any generated file.
	rake clobber_package # Remove package products
	rake clobber_rdoc # Remove rdoc products
	rake package # Build all the packages
	rake prepare-release # Run tests, generate RDoc and create packages.
	rake publish # Publish new release of MyProject
	rake publish-website # Upload website to RubyForge. scp will prompt for your
	 # RubyForge password.
	rake rdoc # Build the rdoc HTML Files
	rake repackage # Force a rebuild of the package files
	rake rerdoc # Force a rebuild of the RDOC files
	rake test # Run tests for test

Heres the idea behind prepare-release and publish: suppose you get a bug report and you need to do a new release. You fix the bug and add a test case to make sure it stays fixed. You check your fix by running the tests with rake (or rake test). Then you edit a library file and bump up the projects version number.

Now that you e confident the bug is fixed, you can run rake publish. This task builds your package, tests it, packages it, and uploads it to RubyForge. You didn have to do any work besides fix the bug and increment the version number.

The rubyforge script is a command-line tool that performs common interactions with RubyForge, like the creation of new releases. To use the publish task, you need to install the rubyforge script and do some basic setup for it. The alternative is to use the prepare-release task instead of publish, and upload all your new packages manually.

Note that Rake uses the zip and tar command-line tools to create the ZIP file and tarball packages. These tools are not available on most Windows installations. If you e on windows, set the attributes need_tar and need_zip of the Rake::GemPackageTask to false. With these attributes, the package task only creates a gem package.

See Also

  • Recipe 19.4, "Automatically Building a Gem"
  • You can download the rubyforge script from http://rubyforge.org/projects/codeforpeople/


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net