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