Tech /

Rake For Make Users





edit SideBar

Rake For Make Users

A guide to switching from Make to Rake.

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

1.  Syntax

1.1  Variables

Immediate evaluation assignment.

PROJ := MyDoc
DOCS := $(PROJ).html $(PROJ).odt
PROJ = "MyDoc"
DOCS = [ "#{PROJ}.html", "#{PROJ}.odt" ]

Lazy evaluation assignment.

  TODO

1.2  Targets

images: $(IMAGES)
desc "Build images"
task :images => IMAGES

1.3  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

1.4  Globs and Patsubst

SRC := $(wildcard *.c)
OBJ := $(patsubst %.c,%.o,$(SRC))
SRC = FileList['*.c']
OBJ = SRC.ext('o')

1.5  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.

1.6  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(" ")

1.7  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.

1.8  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
 

1.9  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

1.10  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 0
end

task :clean => :phony

2.  Examples

2.1  Single build tree

RakeExampleBuildDirectory

2.2  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
 

2.3  Putting it all together

My rakefile template for building C projects.

3.  Extras

3.1  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

4.  Links

---

Recent Changes (All) | Edit SideBar Page last modified on 05 August 2015, at 01:26 PM UTC Edit Page | Page History
Powered by PmWiki