Rakefileでgccを使ってみた

約半年振りに友達と飲みに行き、その後カラオケに行ったところ、自分以外の全員の終電が無くなったため、始発までカラオケすることになりました。
数年振りのオールだったため、後半にはマジでくたばっていましたが、職場で行く飲み会とはやっぱり違いますね。たまにはこういうのがないとやってられないです。
そのかわり土曜の午後から日曜の昼過ぎまでくたばってましたが。

まぁそんな私事はさておき。
私は趣味で C++ を使ってゴニョゴニョやっているんですが、autoconf, automake を利用して開発を進めていました。しかし、automakeだけではなく、せっかくrubyも(少し)使えるようになってきたので、Rakeを使ってみることにしました。

Rakefileの詳しい記述は、調べてもらったほうが早いため、割愛します。RakefileではおよそMakefileで出来ることはすべて記述できるため、なんでもできるんですが、やっぱり利用可能なライブラリとかは、configureに調べてもらった方が正確だと思いますので、configureからRakefileを生成したいものです。

これは、configure.in の AC_CONFIG_FILES に以下の様に記述し、「Rakefile.in」というファイルを作成することで、Makefileと同様に Rakefile に@top_srcdir@などを利用することができます。

AC_CONFIG_FILES([Rakefile
                 src/Rakefile])

さて、この Rakefile.in ですが、これは普通の Rakefile に@EXEEXT@などを入れることができる以外はただのRakefileと変わりません。で、私はあまり Makefile をいじったことがありませんので、どうにも書き方が微妙になってしまいました。
実際に書いて利用しているRakefileは以下のようなものです。

# -*- coding: utf-8 -*-
require "rake/clean"

CC = "@CXX@"
cflags = "@CFLAGS@ -fno-default-inline"

TOPDIR = "@top_srcdir@"

ldflags = "@DEFS@ "
ldflags += "-L@top_srcdir@/test/gtest/gtest-all"

includes = " "
includes += "-I. -I@top_srcdir@"

# lists of compile dependency objects
SRCS = FileList["*.cpp"]
OBJS = SRCS.ext("o")
OBJECTS = FileList["@top_srcdir@/src/**/*.o"]

TARGET_OBJECTS = FileList[]
TARGET_OBJECTS.include(OBJS)
TARGET_OBJECTS.include(OBJECTS)

# lists of running test program
TEST_PROGRAMS = FileList["*_test.cpp"]
TEST_PROGRAMS_EXE = TEST_PROGRAMS.ext("")
EXEEXT = "@EXEEXT@"

# lists of clean objects
CLEAN.include(OBJS)
CLOBBER.include(TEST_PROGRAMS)

DEPDIR = "./.deps"

task "default" => [DEPDIR, "compile"]
task "test" => ["compile", "execute"]

directory DEPDIR
file DEPDIR do |t|
  rm_f t.name + "/*.po"
end

desc "Compile all sources "
task "compile" => OBJS do |t|
  cd("../src") do |f|
    sh "rake -f ../src/Rakefile --trace"
  end
end

rule "_test#{EXEEXT}" => TARGET_OBJECTS do |t|
  sh "#{CC} -o #{t.name} #{ldflags} #{cflags} #{includes} #{OBJECTS.join(' ')} ../test/gtest/gtest-all.o #{t.source} "
end

rule '.o' => '.cpp' do |t|
  depname = File.basename(t.name, ".o") + ".po"
  sh "#{CC} -MD -MF #{DEPDIR}/#{depname} #{ldflags} #{cflags} #{includes} -c #{t.source}"
end

desc "run all test program in `TEST_PROGRAMS`"
task :execute => TEST_PROGRAMS_EXE do |t|
  t.prerequisites.each { |f|
    sh "./#{f}#{EXEEXT} --gtest_color=yes"
  }
end

gccを利用するため、@CXX@としてコンパイラを指定しています。./configureを利用する利点は、こういうことなんだと理解しました。
この Rakefile.in で記述しているgccへのオプションですが、 -MD とか -MF とか付与されています。これはgccプリプロセッサへの指定で、

  • MD : プリプロセッサを展開し、依存しているヘッダーを抽出します。コンパイルはちゃんと行われます。
  • MF : MDによって抽出された依存ヘッダーを、指定したファイル名に出力します。

MDには、MMとかMとかもありますが、依存性だけ調べる → MM 、コンパイルと依存性チェックを行う → MD という形になるかと思います。多分ですが。
このRakefileでは、src/以下にソースファイルがあり、test/以下にテスト関係の全てのファイルが存在する、という前提となっていて、src/以下のソースが更新されたら、ちゃんとそれを検知してtest/以下のプログラムも更新されるようにしています。
・・・が、方法がよくわからず、非効率な処理になってしまっています。あと、実行するディレクトリをそのRakefileがあるディレクトリにする、というのをスマートにやる方法がわからず、cdとブロックを合わせて利用しています。もっとスマートな方法はあるんだろうか。

なんかすばらしくダラダラとした文になってしまいました。まだダメージが殘っているようです。

次に記事を書く時にはもっと有用な記事を書きたいものです。カラオケオールはほどほどに。