A guide to switching from Make to Rake.

If you're just after a Rakefile for building C code, use my Rake Template.

Syntax

Variables

Immediate evaluation assignment.

PROJ := MyDoc
DOCS := $(PROJ).html $(PROJ).odt



PROJ = "MyDoc"
DOCS = [ "#{PROJ}.html", "#{PROJ}.odt" ]

Lazy evaluation assignment.

TODO

Targets

images: $(IMAGES)



desc "Build images"
task :images => IMAGES

Rules

Create .html files by combining .rst and .svg:

%.html : %.rst %.svg
    rst2html.py $< $@



rule '.html' => ['.rst','.svg'] do |t|
        sh "rst2html.py #{t.source} #{t.name}"
end

Globs and Patsubst

SRC := $(wildcard *.c)
OBJ := $(patsubst %.c,%.o,$(SRC))



SRC = FileList['*.c']
OBJ = SRC.ext('o')

Output Paths

Generate output files with names based on the input files. Support output to a separate build directory.

Using pathmap

To create an OBJ list with an output path in Rake we can use the pathmap syntax %X and substitute known paths %{obj,src}.

Pathmap is limited though. Substituting a variable like OBJDIR in the pathmap syntax for FileLists doesn't work. It should work with the '' symbol allowing one to specify a block to do the required substitution, but FileList breaks this, substituting a literal ''.

The mapping of SRC to OBJ needs to be reversed in the '.o' generation rule. Without using a proc this example needs to repeat the output path in multiple places. (Not very DRY.)

Note also that the substitution depends on SRC paths starting with './'.

DRYer, more flexible mapping is achieved with a map and a proc. See below.

OBJDIR := obj
OBJS := ${OBJDIR}/foo.o

$(OBJDIR)/%.o : src/%.c
    @mkdir -p $(dir $@)
    $(CC) -c -o $@ $<



OBJDIR = "obj"
OBJS = C_SRCS.pathmap('%{.,path/to/obj/dir}X.o')

rule '.o' => ['%{path/to/obj/dir,.}X.c', '%d'] do |t|
   mkdir_p t.name.pathmap('%d')
   sh "#{CC} #{t.source} -o #{t.name} #{STDLIB\_CFLAGS} #{STDLIB\_LFLAGS}"
end

Using map and proc

Use map and proc to map source trees to output trees in a flexible way.

# Map sources to objects
OBJS = C_SRCS.map { |f|
      f.sub(/^#{SRC_DIR}/, OBJDIR).ext('.o')
}

# Map objects to sources
rule '.o' => [
    proc { |tn| tn.sub(/#{OBJ\_DIR}/, SRC\_DIR).ext('.c') },
     '%d'
 ] do |t|
 sh %Q{#{CC} -c "#{t.source}" -o "#{t.name}"}
end

An example showing how to use procs to map outputs to a build directory. RakeExampleBuildDirectory

Creating directories

In GNUMake, use a silent mkdir -p and the dir built-in.

$(OBJDIR)/%.o : src/%.c
    @mkdir -p $(dir $@)
    $(CC) -c -o $@ $<

In Rake to create output directories to mimic the source tree layout we could use the same method as Make with the mkdir_p method but this still calls mkdir every time the rule is applied.

A more efficient way is to:

  • Create a set of directory rules based on the OBJS paths
  • Make the generation rule depend on both the object file and its directory (using pathmap syntax).

    declare directory tasks for each object file path

    OBJS.each do |d|   directory d.pathmap('%d') end

    Depend on the source file and the output directory

    rule '.o' => ['%{path/to/obj/dir,.}X.c', '%d'] do |t|   sh "#{CC} #{C_FLAGS} #{C_DEFINE} #{C_INCLUDE} #{t.source} -o#{t.name}" end

The source file must come first for 't.source' to work correctly. Here the pathmap syntax '%d' does the work of extracting the path from the filepath.

Include Paths and Define lists

Mapping an array of include paths to a string passed to the compiler.

INCLUDE_DIRS = [
  ".",
  "inc",
  "/usr/include/blah",
]

# Map to the compiler's command line format
C\_INCLUDE = INCLUDE\_DIRS.map {|s| "-I "+s }.join(" ")

Spaces in Filenames

The Easy Way with Shellwords

Require shellwords

require 'shellwords'

rule '.out' => [*C_OBJS, '%d'] do |t|
  sh %Q{#{CC} -o #{t.name.shellescape} #{C_OBJS.shelljoin}}
end

The Manual Way with Monkey-patching

If you're not using Bash as your shell you may have to do some manual substitution. In rake, we insert ("monkey patch" or "duck punch") a method in FileList and Array to convert each to a list of quoted strings.

# Extension to quote lists (of object files, for example)
class Array
  def to\_quoted\_s(q='"')
  "#{q}#{self.join("#{q} #{q}")}#{q}"
  end
end

class FileList
  def to\_quoted\_s(q='"')
    self.to_a.to\_quoted\_s(q)
  end
end

Then a rule looks like:

rule '.out' => [*C_OBJS, '%d'] do |t|
  sh %Q{#{CC} #{LD\_FLAGS} #{C\_FLAGS} #{C\_DEFINE} #{C\_INCLUDE} -o "#{t.name}" #{C\_OBJS.to\_quoted_s}}
end

This could be extended (with a map-join or inject) to escape cases where the quote character appears in the filenames themselves.

OS Detection

Make

UNAME := $(shell uname)

ifeq ($(UNAME), Linux)
# do something Linux-y
endif
ifeq ($(UNAME), Solaris)
# do something Solaris-y
endif

ifdef SystemRoot
   RM = del /Q
   FixPath = $(subst /,\,$1)
else
   ifeq ($(shell uname), Linux)
      RM = rm -f
      FixPath = $1
   endif
endif

Rake

def os
  @os ||= (
    host_os = RbConfig::CONFIG['host_os']
    case host_os
    when /mswin|bccwin|wince|emc/
      :windows
    when /cygwin/
      :cygwin
    when /mingw|msys/
      :mingw
    when /darwin|mac os/
      :macosx
    when /linux/
      :linux
    when /solaris|bsd/
      :unix
    else
      :unknown
    end
  )
end
 

Invocation and Arguments

Make pulls all environment variables into the same namespace as Make variables. Rake accesses arguments as if they were environment variables.

Make

CONFIG ?= Release

Invocation:

$ make CONFIG=Debug

Rake

CONFIG = ENV["CONFIG"] || "Release"

$[Get Code]39

$ rake CONFIG=Debug

PHONY

Make

.PHONY: clean

rake

As of 0.9.3 rake has phony built in:

require 'rake/phony'
task :clean => :phony

But it can be simulated with:

def (task(:phony)).timestamp
  Time.at 
end

task :clean => :phony

Examples

Single build tree

RakeExampleBuildDirectory

Make and Rake

An example project which takes a ReStructuredText document and graphviz/dot files and generates a single html or odt document.

The html document has SVG images, the odt document has png images.

GNUmake

#
# GNUmakefile for dot and reStructuredText
#

##### Inputs ######

PROJ := MyDoc

DOCS := $(PROJ).html $(PROJ).odt
IMAGES := $(PROJ).svg $(PROJ).png

##### Targets ######

.PHONY: all clean images
all: docs
docs: $(DOCS)
images: $(IMAGES)

clean:
    @-$(RM) $(DOCS) $(IMAGES)

##### Rules ######

%.svg : %.dot
    dot -Tsvg $< -o $@

%.png : %.dot
    dot -Tpng $< -o $@

%.html : %.rst %.svg
    rst2html.py $< $@

%.odt : %.rst %.png
    sed 's/\.svg/\.png/g' $< | rst2odt.py > $@

Rake

# Rakefile for dot and reStructuredText
#
require 'rake/clean'

##### Inputs ######

PROJ = "MyDoc"

DOCS = [ "#{PROJ}.html", "#{PROJ}.odt" ]
IMAGES = [ "#{PROJ}.svg", "#{PROJ}.png" ]

##### Targets ######

CLEAN.include DOCS, IMAGES

desc "Build documents"
task :docs => DOCS

desc "Build images"
task :images => IMAGES

desc "Build all"
task :default => 'docs'

##### Rules ######

rule '.svg' => ['.dot'] do |t|
        sh "dot -Tsvg #{t.source} -o #{t.name}"
end

rule '.png' => ['.dot'] do |t|
        sh "dot -Tpng #{t.source} -o #{t.name}"
end

rule '.html' => ['.rst','.svg'] do |t|
        sh "rst2html.py #{t.source} #{t.name}"
end

rule '.odt' => ['.rst','.png'] do |t|
        sh "sed 's/\.svg/\.png/g' #{t.source} | rst2odt.py > #{t.name}"
end
 

Putting it all together

My rakefile template for building C projects.

Extras

Task iteration

From stackoverflow #1290119.

Ruby tasks, when invoked, are marked as complete. Repeated calls (e.g. in a loop) will invoke only once.

The solution is either to:

  • convert the task to a method
  • reenable the task after each call

As A Method

task :build => [:some\_other\_tasks] do
  build
end

task :build_all do
  [:debug, :release].each { |t| build t }
end

def build(type = :debug)
  # ...
end

Reenabling tasks

Rake::Task[':build'].reenable
Rake::Task[':build'].invoke

Links

---|