Ruby and Its World - Ruby in Its Setting - Programming Ruby 1.9 & 2.0: The Pragmatic Programmers’ Guide (2013)

Programming Ruby 1.9 & 2.0: The Pragmatic Programmers’ Guide (2013)

Part 2. Ruby in Its Setting

Chapter 15. Ruby and Its World

It’s an unfortunate fact of life that our applications have to deal with the big, bad world. In this chapter, we’ll look at how Ruby interacts with its environment. Microsoft Windows users will probably also want to look at the platform-specific information.

15.1 Command-Line Arguments

“In the beginning was the command line.”[63] Regardless of the system in which Ruby is deployed, whether it be a super-high-end scientific graphics workstation or an embedded PDA device, you have to start the Ruby interpreter somehow, and that gives us the opportunity to pass in command-line arguments.

A Ruby command line consists of three parts: options to the Ruby interpreter, optionally the name of a program to run, and optionally a set of arguments for that program:

ruby <options> <--> <programfile> <arguments>*

The Ruby options are terminated by the first word on the command line that doesn’t start with a hyphen or by the special flag -- (two hyphens).

If no filename is present on the command line or if the filename is a single hyphen, Ruby reads the program source from standard input.

Arguments for the program itself follow the program name. For example, the following:

$ ruby -w - "Hello World"

will enable warnings, read a program from standard input, and pass it the string "Hello World" as an argument.

Command-Line Options

-0[octal]

The 0 flag (the digit zero) specifies the record separator character (\0, if no digit follows). -00 indicates paragraph mode: records are separated by two successive default record separator characters. \0777 reads the entire file at once (because it is an illegal character). Sets $/.

-a

Autosplit mode when used with -n or -p; equivalent to executing $F = $_.split at the top of each loop iteration.

-C directory

Changes working directory to directory before executing.

-c

Checks syntax only; does not execute the program.

--copyright

Prints the copyright notice and exits.

-d, --debug

Sets $DEBUG and $VERBOSE to true. This can be used by your programs to enable additional tracing.

--disable-all

Disable the rubygems and RUBYOPT options (see the following descriptions).«2.0»

--disable-gems

Stops Ruby from automatically loading RubyGems from require . There is a corresponding --enable-gems option.

--disable-rubyopt

Prevents Ruby from examining the RUBYOPT environment variable. You should probably set this in an environment you want to secure. There is a corresponding --enable-rubyopt option.«2.0»

--dump option…

Tells Ruby to dump various items of internal state. options… is a comma or space separated list containing one or more of copyright, insns, parsetree, parsetree_with_comment, syntax, usage, version, and yydebug. This is intended for Ruby core developers.

--enable-all

Enable the rubygems and RUBYOPT options (see the following descriptions).«2.0»

--enable-gems

Allows Ruby to automatically load RubyGems from require . There is a corresponding --disable-gems option.

--enable-rubyopt

Allows Ruby to use the RUBYOPT environment variable. (This is the default.) You should probably disable this option in an environment you want to secure.«2.0»

-E encoding, --encoding encoding, --encoding=encoding

Specifies the default character encoding for data read from and written to the outside world. This can be used to set both the external encoding (the encoding to be assumed for file contents) and optionally the default internal encoding (the file contents are transcoded to this when read and transcoded from this when written). The format of the encoding parameter is -E external, -E external:internal, or -E :internal. See Chapter 17, Character Encoding for details. See also -U.

-e ’command’

Executes command as one line of Ruby source. Several -e’s are allowed, and the commands are treated as multiple lines in the same program. If programfile is omitted when -e is present, execution stops after the -e commands have been run. Programs run using -e have access to the old behavior of ranges and regular expressions in conditions—ranges of integers compare against the current input line number, and regular expressions match against $_.

--external-encoding=encoding

Specifies the default external coding for the program.«2.0»

-F pattern

Specifies the input field separator ($;) used as the default for split (affects the -a option).

-h, --help

Displays a short help screen.

-I directories

Specifies directories to be prepended to $LOAD_PATH ($:). Multiple -I options may be present. Multiple directories may appear following each -I, separated by a colon on Unix-like systems and by a semicolon on DOS/Windows systems.

-i [extension]

Edits ARGV files in place. For each file named in ARGV, anything you write to standard output will be saved back as the contents of that file. A backup copy of the file will be made if extension is supplied.

$ ruby -pi.bak -e "gsub(/Perl/, 'Ruby')" *.txt

--internal-encoding=encoding

Specifies the default internal coding for the program.«2.0»

-l

Enables automatic line-ending processing; sets $\ to the value of $/ and chops every input line automatically.

-n

Assumes a while gets; ...; end loop around your program. For example, a simple grep command could be implemented as follows:

$ ruby -n -e "print if /wombat/" *.txt

-p

Places your program code within the loop while gets; ...; print; end.

$ ruby -p -e "$_.downcase!" *.txt

-r library

Requires the named library or gem before executing.

-S

Looks for the program file using the RUBYPATH or PATH environment variable.

-s

Any command-line switches found after the program filename, but before any filename arguments or before a --, are removed from ARGV and set to a global variable named for the switch. In the following example, the effect of this would be to set the variable $opt to "electric":

$ ruby -s prog -opt=electric ./mydata

-Tlevel

Sets the safe level, which among other things enables tainting and untrusted checks (see Chapter 26, Locking Ruby in the Safe). Sets $SAFE.

-U

Sets the default internal encoding to UTF-8. See Chapter 17, Character Encoding for details. See also -E.

-v, --verbose

Sets $VERBOSE to true, which enables verbose mode. Also prints the version number. In verbose mode, compilation warnings are printed. If no program filename appears on the command line, Ruby exits.

--version

Displays the Ruby version number and exits.

-w

Enables verbose mode. Unlike -v, reads program from standard input if no program files are present on the command line. We recommend running your Ruby programs with -w.

-W level

Sets the level of warnings issued. With a level of two (or with no level specified), equivalent to -w—additional warnings are given. If level is 1, runs at the standard (default) warning level. With -W0, absolutely no warnings are given (including those issued using Object#warn).

-X directory

Changes working directory to directory before executing. This is the same as -C directory.

-x [directory]

Strips off text before #!ruby line and changes working directory to directory if given.

-y, --yydebug

Enables yacc debugging in the parser (waaay too much information).

Argument Processing: ARGV and ARGF

Any command-line arguments after the program filename are available to your Ruby program in the global array ARGV. For instance, assume test.rb contains the following program:

ARGV.each {|arg| p arg }

Invoke it with the following command line:

$ ruby -w test.rb "Hello World" a1 1.6180

It’ll generate the following output:

"Hello World"

"a1"

"1.6180"

There’s a gotcha here for all you C programmers—ARGV[0] is the first argument to the program, not the program name. The name of the current program is available in the global variable $0, which is aliased to $PROGRAM_NAME. Notice that all the values in ARGV are strings.

If your program reads from standard input (or uses the special object ARGF, described in the next section), the arguments in ARGV will be taken to be filenames, and Ruby will read from these files. If your program takes a mixture of arguments and filenames, make sure you empty the nonfilename arguments from the ARGV array before reading from the files.

ARGF

It is common for a command line program to take a list of zero or more file names to process. It will then read through these files in turn, doing whatever it does.

Ruby provides a convenience object, referenced by the name ARGF, that handles access to these files. When your program starts, ARGF is initialized with a reference ARGV. Because this is a reference, changes to make to ARGV (for example when you remove options as you process them) are seen by ARGF.

If you read from ARGF (for example by calling ARGF.gets) or from standard input (for example by calling plain gets ), Ruby will open the file whose name is the first element of ARGV and perform the I/O on it. If, as you continue to read, you reach the end of that file, Ruby closes it, shifts it out of the ARGV array, and then opens the next file in the list. At some point, when you finishing reading from the last file, ARGV will return an end-of-file condition (so gets will return nil, for example). If ARGV is initially empty, ARGF will read from standard input.

You can get to the name of the file currently being read from using ARGF.filename, and you can get the current File object as ARGF.file. ARGF keeps track of the total number of lines read in ARGF.lineno—if you need the line number in the current file, use ARGV.file.lineno. Here’s a program that uses this information:

while line = gets

printf "%d: %10s[%d] %s", ARGF.lineno, ARGF.filename, ARGF.file.lineno, line

end

If we run it, passing a couple of file names, it will copy the contents of those files.

$ ruby copy.rb testfile otherfile

1: testfile[1] This is line one

2: testfile[2] This is line two

3: testfile[3] This is line three

4: testfile[4] And so on...

5: otherfile[1] ANOTHER LINE ONE

6: otherfile[2] AND ANOTHER LINE TWO

7: otherfile[3] AND FINALLY THE LAST LINE

In-place Editing

In-place editing is a hack inherited from Perl. It allows you to alter the contents of files passed in on the command line, retaining a backup copy of the original contents.

To turn on in-place editing, give Ruby the file extension to use for the backup file, either with the -i [ext] command line option, or by calling ARGF.inplace_mode=ext in your code.

Now, as your code reads through each file given on the command line, Ruby will rename the original file by appending the backup extension. It will then create a new file with the original name, and open it for writing on standard output. This all means that if you code a program such as this:

while line = gets

puts line.chomp.reverse

end

and you invoked it using

$ ruby -i.bak reverse.rb testfile otherfile

You’d find that testfile and otherfile would now have reversed lines, and that the original files would be available in testfile.bak and otherfile.bak.

For finer control over the I/O to these files, you can use the methods provided by ARGF. They’re rarely used, so rather than document them here, we’ll refer you to ri or the online documentation.

15.2 Program Termination

The method Object#exit terminates your program, returning a status value to the operating system. However, unlike some languages, exit doesn’t terminate the program immediately— exit first raises a SystemExit exception, which you may catch, and then performs a number of cleanup actions, including running any registered at_exit methods and object finalizers. See the reference for Object#at_exit.

15.3 Environment Variables

You can access operating system environment variables using the predefined variable ENV. It responds to the same methods as Hash.[64]

ENV['SHELL']

ENV['HOME']

ENV['USER']

ENV.keys.size

ENV.keys[0, 4]

The values of some environment variables are read by Ruby when it first starts. These variables modify the behavior of the interpreter.

The environment variables used by Ruby are listed in the following table.

Variable Name

Description

DLN_LIBRARY_PATH

Specifies the search path for dynamically loaded modules.

HOME

Points to user’s home directory. This is used when expanding ~ in file and directory names.

LOGDIR

Specifies the fallback pointer to the user’s home directory if $HOME is not set. This is used only by Dir.chdir.

OPENSSL_CONF

Specifies the location of OpenSSL configuration file.

RUBYLIB

Specifies an additional search path for Ruby programs ($SAFE must be 0).

RUBYLIB_PREFIX

(Windows only) Mangles the RUBYLIB search path by adding this prefix to each component.

RUBYOPT

Specifies additional command-line options to Ruby; examined after real command-line options are parsed ($SAFE must be 0).

RUBYPATH

With -S option, specifies the search path for Ruby programs (defaults to PATH).

RUBYSHELL

Specifies shell to use when spawning a process under Windows; if not set, will also check SHELL or COMSPEC.

RUBY_TCL_DLL

Overrides default name for Tcl shared library or DLL.

RUBY_TK_DLL

Overrides default name for Tk shared library or DLL. Both this and RUBY_TCL_DLL must be set for either to be used.

Other environment variables affect the memory allocated by the Ruby virtual machine for various tasks.[65]«2.0»

Variable Name

Description

RUBY_THREAD_VM_STACK_SIZE

The VM stack size used at thread creation: 128KB (32 bit CPU) or 256KB (64 bit CPU).

RUBY_THREAD_MACHINE_STACK_SIZE

The machine stack size used at thread creation: 512KB (32 bit CPU) or 1024KB (64 bit CPU).

RUBY_FIBER_VM_STACK_SIZE

VM stack size used at fiber creation: 64KB or 128KB.

RUBY_FIBER_MACHINE_STACK_SIZE

The machine stack size used at fiber creation: 256KB or 256KB.

The current value of these variables can be read using RubyVM::DEFAULT_PARAMS.

Writing to Environment Variables

A Ruby program may write to the ENV object. On most systems, this changes the values of the corresponding environment variables. However, this change is local to the process that makes it and to any subsequently spawned child processes. This inheritance of environment variables is illustrated in the code that follows. A subprocess changes an environment variable, and this change is inherited by a process that it then starts. However, the change is not visible to the original parent. (This just goes to prove that parents never really know what their children are doing.)

puts "In parent, term = #{ENV['TERM']}"

fork do

puts "Start of child 1, term = #{ENV['TERM']}"

ENV['TERM'] = "ansi"

fork do

puts "Start of child 2, term = #{ENV['TERM']}"

end

Process.wait

puts "End of child 1, term = #{ENV['TERM']}"

end

Process.wait

puts "Back in parent, term = #{ENV['TERM']}"

Produces:

In parent, term = xterm-256color

Start of child 1, term = xterm-256color

Start of child 2, term = ansi

End of child 1, term = ansi

Back in parent, term = xterm-256color

Setting an environment variable’s value to nil removes the variable from the environment.

15.4 Where Ruby Finds Its Libraries

You use require or load to bring a library into your Ruby program. Some of these libraries are supplied with Ruby, some you may have installed from the Ruby Application Archive, some may have been packaged as RubyGems (of which more later), and some you may have written yourself. How does Ruby find them?

Let’s start with the basics. When Ruby is built for your particular machine, it predefines a set of standard directories to hold library stuff. Where these are depends on the machine in question. You can determine this from the command line with something like this:

$ ruby -e 'puts $:'

On our OS X box, with RVM installed, this produces the following list:

/Users/dave/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/site_ruby/2.0.0

/Users/dave/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/site_ruby/2.0.0/x86_64-darwin12.2.0

...

The site_ruby directories are intended to hold modules and extensions that you’ve added. The architecture-dependent directories (x86_64-darwin... in this case) hold executables and other things specific to this particular machine. All these directories are automatically included in Ruby’s search for libraries.

Sometimes this isn’t enough. Perhaps you’re working on a large project written in Ruby and you and your colleagues have built a substantial library of Ruby code. You want everyone on the team to have access to all this code. You have a couple of options to accomplish this. If your program runs at a safe level of zero (see Chapter 26, Locking Ruby in the Safe), you can set the environment variable RUBYLIB to a list of one or more directories to be searched.[66] If your program is not setuid, you can use the command-line parameter -I to do the same thing.

The Ruby variable $: is an array of places to search for loaded files. As we’ve seen, this variable is initialized to the list of standard directories, plus any additional ones you specified using RUBYLIB and -I. You can always add directories to this array from within your running program. Prior to Ruby 1.9, this used to be a common idiom:

$: << File.dirname(__FILE__)

require 'other_file'

This added the directory of the running file to the search path, so other_file.rb could be found there by the subsequent require . Now we use require_relative instead.

require_relative 'other_file'

15.5 RubyGems Integration

This section is based on the start of the chapter on RubyGems written by Chad Fowler for the second edition of this book.

RubyGems is a standardized packaging and installation framework for Ruby libraries and applications. RubyGems makes it easy to locate, install, upgrade, and uninstall Ruby packages.

Before RubyGems came along, installing a new library involved searching the Web, downloading a package, and attempting to install it—only to find that its dependencies hadn’t been met. If the library you want is packaged using RubyGems, however, you can now simply ask RubyGems to install it (and all its dependencies). Everything is done for you.

In the RubyGems world, developers bundle their applications and libraries into single files called gems . These files conform to a standardized format and typically are stored in repositories on the Internet (but you can also create your own repositories if you want).

The RubyGems system provides a command-line tool, appropriately named gem, for manipulating these gem files. It also provides integration into Ruby so that your programs can access gems as libraries.

Prior to Ruby 1.9, it was your responsibility to install the RubyGems software on your computer. Now, however, Ruby comes with RubyGems baked right in.

Installing Gems on Your Machine

Your latest project calls for a lot of XML generation. You could just hard-code it, but you’ve heard about Jim Weirich’s Builder library, which constructs XML directly from Ruby code.

Let’s start by seeing whether Builder is available as a gem:

$ gem query --details --remote --name-matches builder

AntBuilder (0.4.3)

Author: JRuby-extras

Homepage: http://jruby-extras.rubyforge.org/

AntBuilder: Use ant from JRuby. Only usable within JRuby

builder (2.1.2)

Author: Jim Weirich

Homepage: http://onestepback.org

Builders for MarkUp.

The --details option displays the descriptions of any gems it finds. The --remote option searches the remote repository. And the --name-matches option says to search the central gem repository for any gem whose name matches the regular expression /builder/. (We could have used the short-form options -d, -r, and -n.) The result shows a number of gems have builder in their name; the one we want is just plain builder.

The number after the name shows the latest version. You can see a list of all available versions using the --all option. We’ll also use the list command, as it lets us match on an exact name:

$ gem list --details --remote --all builder

*** REMOTE GEMS ***

builder (2.1.2, 2.1.1, 2.0.0, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.2.0, 1.1.0, 0.1.1)

Author: Jim Weirich

Homepage: http://onestepback.org

Builders for MarkUp.

Because we want to install the most recent one, we don’t have to state an explicit version on the install command; the latest is downloaded by default:

$ gem install builder

Successfully installed builder-2.1.2

1 gem installed

Installing ri documentation for builder-2.1.2...

Installing RDoc documentation for builder-2.1.2...

Several things happened here. First, we see that the latest version of the Builder gem (2.1.2) has been installed. Next we see that RubyGems has determined that Jim has created documentation for his gem, so it sets about extracting it using RDoc.

If you’re running gem install on a Unix platform and you aren’t using rvm, you’ll need to prefix the command with sudo, because by default the local gems are installed into shared system directories.

During installation, you can add the -t option to the install command, causing RubyGems to run the gem’s test suite (if one has been created). If the tests fail, the installer will prompt you to either keep or discard the gem. This is a good way to gain a little more confidence that the gem you’ve just downloaded works on your system the way the author intended.

Let’s see what gems we now have installed on our local box:

$ gem list

*** LOCAL GEMS ***

builder (2.1.2)

Reading the Gem Documentation

Being that this is your first time using Builder, you’re not exactly sure how to use it. Fortunately, RubyGems installed the documentation for Builder on your machine. We just have to find it.

As with most things in RubyGems, the documentation for each gem is stored in a central, protected, RubyGems-specific place. This will vary by system and by where you may explicitly choose to install your gems. The most reliable way to find the documents is to ask the gem command where your RubyGems main directory is located:

$ gem environment gemdir

/usr/local/lib/ruby/gems/1.9.3

RubyGems stores generated documentation beneath the doc/ subdirectory of this directory.

The easiest way to view gems’ RDoc documentation is to use RubyGems’ included gem server utility. To start gem server, simply type this:

$ gem server

Server started at http://[::ffff:0.0.0.0]:8808

Server started at http://0.0.0.0:8808

gem server starts a web server running on whatever computer you run it on. By default, it will start on port 8808 and will serve gems and their documentation from the default RubyGems installation directory. Both the port and the gem directory are overridable via command-line options, using the -p and -d options, respectively.

Once you’ve started the gem server, if you are running it on your local computer, you can access the documentation for your installed gems by pointing your web browser to http://localhost:8808 . There, you will see a list of the gems you have installed with their descriptions and links to their RDoc documentation. Click the rdoc link for Builder—the result will look something like the following.

Using a Gem

Once a gem is installed, you use require to load it into your program:[67]

require 'builder'

xml = Builder::XmlMarkup.new(target: STDOUT, indent: 2)

xml.person(type: "programmer") do

xml.name do

xml.first "Dave"

end

xml.location "Texas"

xml.preference("ruby")

end

Produces:

<person type="programmer">

<name>

<first>Dave</first>

</name>

<location>Texas</location>

<preference>ruby</preference>

</person>

Gems and Versions

Maybe you first started using Builder a few years ago. Back then the interface was a little bit different—with versions prior to Build 1.0, you could say this:

xml = Builder::XmlMarkup.new(STDOUT, 2)

xml.person do

name("Dave Thomas")

end

Note that the constructor takes positional parameters. Also, in the do block, we can say just name(...), whereas the current Builder requires xml.name(...). We could go through our old code and update it all to work with the new-style Builder—that’s probably the best long-term solution. But we can also let RubyGems handle the issue for us.

When we asked for a listing of the Builder gems in the repository, we saw that multiple versions were available:[68]

$ gem list --details --remote --all builder

*** REMOTE GEMS ***

builder (2.1.2, 2.1.1, 2.0.0, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.2.0, 1.1.0, 0.1.1)

When we installed Builder previously, we didn’t specify a version, so RubyGems automatically installed the latest. But we can also get it to install a specific version or a version meeting some given criteria. Let’s install the most recent release of Builder with a version number less than 1:

$ gem install builder --version '< 1'

Successfully installed builder-0.1.1

1 gem installed

Installing ri documentation for builder-0.1.1...

Installing RDoc documentation for builder-0.1.1...

Have we just overwritten the 2.1.2 release of Builder that we’d previously installed? Let’s find out by listing our locally installed gems:

$ gem list builder

*** LOCAL GEMS ***

builder (2.1.2, 0.1.1)

Now that we have both versions installed locally, how do we tell our legacy code to use the old one while still having our new code use the latest version? It turns out that require automatically loads the latest version of a gem, so the earlier code will work fine. If we want to specify a version number when we load a gem, we have to do a little bit more work, making it explicit that we’re using RubyGems:

gem 'builder', '< 1.0'

require 'builder'

xml = Builder::XmlMarkup.new(STDOUT, 2)

xml.person do

name("Dave Thomas")

location("Texas")

end

The magic is the gem line, which says, “When looking for the Builder gem, consider only those versions less than 1.0.” The subsequent require honors this, so the code loads the correct version of Builder and runs. The "< 1.0" part of the gem line is a version predicate. The numbers that follow are of the form major.minor.patch_level. The various predicates that RubyGems supports are:

Table 7. Version operators

Operator

Description

=

Exact version match. Major, minor, and patch level must be identical.

!=

Any version that is not the one specified.

>

Any version that is greater (even at the patch level) than the one specified.

<

Any version that is less than the one specified.

>=

Any version greater than or equal to the specified version.

<=

Any version less than or equal to the specified version.

~>

“Boxed” version operator. Version must be greater than or equal to the specified version and less than the specified version after having its minor version number increased by 1. This is to avoid API incompatibilities between minor version releases.

You can specify multiple version predicates, so the following is valid:

gem 'builder', '> 0.1', '< 0.1.5'

Unfortunately, after all this work, there’s a problem. Older versions of Builder don’t run under 1.9 anyway. You can still run this code in Ruby 1.8, but you’d have to update your code to use the new-style Builder if you want to use Ruby 1.9.

Gems Can Be More Than Libraries

As well as installing libraries that can be used inside your application code, RubyGems can also install utility programs that you can invoke from the command line. Often these utilities are wrappers around the libraries included in the gem. For example, Marcel Molina’s AWS:S3 gem is a library that gives you programmatic access to Amazon’s S3 storage facility. As well as the library itself, Marcel provided a command-line utility, s3sh, which lets you interact with your S3 assets. When you install the gem, s3sh is automatically loaded into the same bin/ directory that holds the Ruby interpreter.

There’s a small problem with these installed utilities. Although gems supports versioning of libraries, it does not version command-line utilities. With these, it’s “last one in wins.”

15.6 The Rake Build Tool

As well as the Builder gem, Jim Weirich wrote an incredibly useful utility program called Rake. Prior to Ruby 1.9, you had to install Rake as a separate gem, but it is now included in the base Ruby installation.

Rake was initially implemented as a Ruby version of Make, the common build utility. However, calling Rake a build utility is to miss its true power. Really, Rake is an automation tool—it’s a way of putting all those tasks that you perform in a project into one neat and tidy place.

Let’s start with a trivial example. As you edit files, you often accumulate backup files in your working directories. On Unix systems, these files often have the same name as the original files, but with a tilde character appended. On Windows boxes, the files often have a bak extension.

We could write a trivial Ruby program that deletes these files. For a Unix box, it might look something like this:

require 'fileutils'

files = Dir['*~']

FileUtils::rm files, verbose: true

The FileUtils module defines methods for manipulating files and directories (see the description in the library section). Our code uses its rm method. We use the Dir class to return a list of filenames matching the given pattern and pass that list to rm .

Let’s package this code as a Rake task —a chunk of code that Rake can execute for us.

By default, Rake searches the current directory (and its parents) for a file called Rakefile. This file contains definitions for the tasks that Rake can run.

So, put the following code into a file called Rakefile:

desc "Remove files whose names end with a tilde"

task :delete_unix_backups do

files = Dir['*~']

rm(files, verbose: true) unless files.empty?

end

Although it doesn’t have an rb extension, this is actually just a file of Ruby code. Rake defines an environment containing methods such as desc and task and then executes the Rakefile.

The desc method provides a single line of documentation for the task that follows it. The task method defines a Rake task that can be executed from the command line. The parameter is the name of the task (a symbol), and the block that follows is the code to be executed. Here we can just use rm —all the methods in FileUtils are automatically available inside Rake files.

We can invoke this task from the command line:

$ rake delete_unix_backups

(in /Users/dave/BS2/titles/ruby4/Book/code/rake)

rm entry~

The first line shows us the name of the directory where Rake found the Rakefile (remember that this might be in a directory above our current working directory). The next line is the output of the rm method, in this case showing it deleted the single file entry~.

OK, now let’s write a second task in the same Rakefile. This one deletes Windows backup files.

desc "Remove files with a .bak extension"

task :delete_windows_backups do

files = Dir['*.bak']

rm(files, verbose: true) unless files.empty?

end

We can run this with rake delete_windows_backups.

But let’s say that our application could be used on both platforms, and we wanted to let our users delete backup files on either. We could write a combined task, but Rake gives us a better way—it lets us compose tasks. Here, for example, is a new task:

desc "Remove Unix and Windows backup files"

task :delete_backups => [ :delete_unix_backups, :delete_windows_backups ] do

puts "All backups deleted"

end

The task’s name is delete_backups, and it depends on two other tasks. This isn’t some special Rake syntax: we’re simply passing the task method a Ruby hash containing a single entry whose key is the task name and whose value is the list of antecedent tasks. This causes Rake to execute the two platform-specific tasks before executing the delete_backups task:

$ rake delete_backups

rm entry~

rm index.bak list.bak

All backups deleted

Our current Rakefile contains some duplication between the Unix and Windows deletion tasks. As it is just Ruby code, we can simply define a Ruby method to eliminate this:

def delete(pattern)

files = Dir[pattern]

rm(files, verbose: true) unless files.empty?

end

desc "Remove files whose names end with a tilde"

task :delete_unix_backups do

delete "*~"

end

desc "Remove files with a .bak extension"

task :delete_windows_backups do

delete "*.bak"

end

desc "Remove Unix and Windows backup files"

task :delete_backups => [ :delete_unix_backups, :delete_windows_backups ] do

puts "All backups deleted"

end

If a Rake task is named default, it will be executed if you invoke Rake with no parameters.

You can find the tasks implemented by a Rakefile (or, more accurately, the tasks for which there is a description) using this:

$ rake -T

(in /Users/dave/BS2/titles/ruby4/Book/code/rake)

rake delete_backups # Remove Unix and Windows backup files

rake delete_unix_backups # Remove files whose names end with a tilde

rake delete_windows_backups # Remove files with a .bak extension

This section only touches on the full power of Rake. It can handle dependencies between files (for example, rebuilding an executable file if one of the source files has changed), it knows about running tests and generating documentation, and it can even package gems for you. Martin Fowler has written a good overview of Rake if you’re interested in digging deeper.[69] You might also want to investigate Sake,[70] a tool that makes Rake tasks available no matter what directory you’re in, or Thor,[71] a tool that makes it easy to write Ruby command-line tools.

15.7 Build Environment

When Ruby is compiled for a particular architecture, all the relevant settings used to build it (including the architecture of the machine on which it was compiled, compiler options, source code directory, and so on) are written to the module RbConfig within the library file rbconfig.rb. After installation, any Ruby program can use this module to get details on how Ruby was compiled:

require 'rbconfig'

include RbConfig

CONFIG["host"] # => "x86_64-apple-darwin12.2.0"

CONFIG["libdir"] # => "/Users/dave/.rvm/rubies/ruby-2.0.0-p0/lib"

Extension libraries use this configuration file in order to compile and link properly on any given architecture. If you visit the online page for the previous edition of this book at http://pragprog.com/titles/ruby3 and select the Contents/Extracts tab, you can download a free chapter on writing extension libraries.

Footnotes

[63]

This is the title of a marvelous essay by Neal Stephenson (available online via http://www.cryptonomicon.com/beginning.html ).

[64]

ENV is not actually a hash, but if you need to, you can convert it into a Hash using ENV#to_hash.

[65]

This applies to MRI only.

[66]

The separator between entries is a semicolon on Windows; for Unix, it’s a colon.

[67]

Prior to Ruby 1.9, before you could use a gem in your code, you first had to load a support library called rubygems. Ruby now integrates that support directly, so this step is no longer needed.

[68]

By the time this book reaches you, the list of available versions will likely have changed.

[69]

http://martinfowler.com/articles/rake.html

[70]

http://errtheblog.com/posts/60-sake-bomb

[71]

http://github.com/wycats/thor