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"
$ 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
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
---|