SConsをちょっと便利なMakeとして使ってみる
makeでちょっと複雑なことをやろうとすると結構しんどくなってきたので、SConsを試してみることにした。
まだ全然使いこなせてないが、とりあえずmakeでできることをSConsではどのようにやるのかのメモ。
単純な例
次のようなMakefileを考えてみる。
all: piyo.txt hoge.txt: echo "This is $@" > $@ fuga.txt: echo "This is $@" > $@ piyo.txt: hoge.txt fuga.txt cat $+ > $@
SConsではSConstructというファイルが、makeにおけるMakefileに相当する。SConstructはPythonのスクリプトである。
上記のMakefileと同様の動きをするSConstructは以下のように書ける。
Command("hoge.txt", None, 'echo "This is $TARGET" > $TARGET') Command("fuga.txt", None, 'echo "This is $TARGET" > $TARGET') Command("piyo.txt", ["hoge.txt", "fuga.txt"], 'cat $SOURCES > $TARGET')
Commandに引数として、ターゲット、ソース、アクションを渡す。Makefileでターゲット、ソース、コマンドを書く順と同じなので覚えやすいと思う。
複数のファイルを指定したい時はリストを渡せば良い。
Makefileではソースやターゲットを示す変数として、$+、$<、$@などを用いるが、SConsでは$SOURCES、$SOURCE、$TARGETなどを用いる。どのようなものが使えるかはmanのVariable Substitutionセクションに書いてある。
コマンドライン
makeでmakeコマンドでビルドを実行するのと同様に、SConsでもSConstructのあるディレクトリでsconsというコマンドを叩けばビルドが実行される。
sconsのオプションはmakeと同じものが多く覚えるのは比較的楽だと思われる(-jや-nなど)。
clean
SConsではscons -cを実行すると、ビルドしたターゲットを削除してくれる。clean用のコマンドを書く必要はない。
debug
makeとsconsではどのファイルをビルドするかの決定基準が違っているので、下記のようにそのファイルをビルドする理由を表示させると理解が深まるかもしれない。
scons --debug=explain
依存関係の表示
treeオプションを使うと、依存関係をツリー状に表示できる。
scons --tree=all
ファイル名の操作
makeでは関数を使って、ファイル名やパスを扱うことができる。
all: piyo.txt TXTDIR := txt $(TXTDIR): mkdir -p $@ $(TXTDIR)/hoge.txt:| $(TXTDIR) echo abspath: $(abspath $@) > $@ echo dir: $(dir $@) >> $@ echo notdir: $(notdir $@) >> $@ echo basename: $(basename $@) >> $@ echo suffix: $(suffix $@) >> $@ FUGA_TXT_NAME := fuga.txt FUGA_TXT := $(TXTDIR)/$(FUGA_TXT_NAME) FUGA_TXT_ABSPATH := $(abspath $(FUGA_TXT)) FUGA_TXT_DIR := $(dir $(FUGA_TXT)) FUGA_TXT_NOTDIR := $(notdir $(FUGA_TXT)) FUGA_TXT_BASENAME := $(basename $(FUGA_TXT)) FUGA_TXT_SUFFIX := $(suffix $(FUGA_TXT)) $(FUGA_TXT):| $(TXTDIR) echo abspath: $(FUGA_TXT_ABSPATH) > $@ echo dir: $(FUGA_TXT_DIR) >> $@ echo notdir: $(FUGA_TXT_NOTDIR) >> $@ echo basename: $(FUGA_TXT_BASENAME) >> $@ echo suffix: $(FUGA_TXT_SUFFIX) >> $@ piyo.txt: $(TXTDIR)/hoge.txt $(FUGA_TXT) cat $+ > $@
上記のMakefileと同様の動作をするSConstructは次のように書ける。
os.pathモジュールを使うこともできるが、${TARGET.abspath}のような書き方もできる。この書き方についての説明もmanのVariable Substitutionにある。
import os.path txtdir = "txt" Command(os.path.join(txtdir, "hoge.txt"), None, [ "echo abspath: ${TARGET.abspath} > $TARGET", "echo dir: ${TARGET.dir} >> $TARGET", "echo notdir: ${TARGET.file} >> $TARGET", "echo basename: ${TARGET.base} >> $TARGET", "echo suffix: ${TARGET.suffix} >> $TARGET" ]) fuga_txt_name = "fuga.txt" fuga_txt = os.path.join(txtdir, fuga_txt_name) fuga_txt_abspath = os.path.abspath(fuga_txt) fuga_txt_dir = os.path.dirname(fuga_txt) fuga_txt_notdir = os.path.basename(fuga_txt) fuga_txt_basename = os.path.splitext(fuga_txt)[0] fuga_txt_suffix = os.path.splitext(fuga_txt)[1] Command(fuga_txt, None, """ echo abspath: %(fuga_txt_abspath)s > $TARGET echo dir: %(fuga_txt_dir)s >> $TARGET echo notdir: %(fuga_txt_notdir)s >> $TARGET echo basename: %(fuga_txt_basename)s >> $TARGET echo suffix: %(fuga_txt_suffix)s >> $TARGET """ % locals() ) Command("piyo.txt", [os.path.join(txtdir, "hoge.txt"), fuga_txt], "cat $SOURCES > $TARGET")
関数とか
makeでは関数をうまく使うと記述が簡単になることがある。
TXT_FILES := $(wildcard *.txt) TXT_FILES_SHELL := $(shell echo *.txt) PATSUBST := $(patsubst %.txt,%.TXT,$(TXT_FILES)) SUBST := $(subst World,Sekai,Hello World!) all: echo TXT_FILES: $(TXT_FILES) echo TXT_FILES_SHELL: $(TXT_FILES_SHELL) echo PATSUBST: $(PATSUBST) echo SUBST: $(SUBST)
SConsでも同様のことはできるが、makeの関数のようにひとつのやり方があるわけでなく、Pythonの処理を組み合わせて実現している場合があるので、Pythonについて知らないとわかりにくいかもしれない。makeにある関数をSConsで実現するには、User's Guideの最後のHandling Common Tasksを参考にすると良い。
アクションの中に${}で囲まれたPythonのコードを書くと、実行結果に置き換えられる。
import os, re env = DefaultEnvironment() txt_files = [str(f) for f in Glob("*.txt")] txt_files_shell = os.popen("echo *.txt").read() patsubst = [os.path.splitext(f)[0] + ".TXT" for f in txt_files] subst = re.sub("World", "Sekai", "Hello World!") env["patsubst"] = patsubst all = Command(".", None, """ echo TXT_FILES: ${" ".join(%(txt_files)s)} echo TXT_FILES_SHELL: %(txt_files_shell)s echo PATSUBST: ${" ".join(patsubst)} echo SUBST: %(subst)s """ % locals()) AlwaysBuild(all)
参考にした文献
- http://www.scons.org/doc/HTML/scons-user/book1.html
- http://www.scons.org/doc/production/HTML/scons-man.html
とくに、User's Guideの最後のHandling Common Tasksやmanの最後の書き方の例は参考になる。
今回はMakefileと対応がとれるようにCommandのみを使ったが、Environmentを使って環境を使い分けたり、自分でビルダーを書いたり、オプションを使いなしたりすればより効率的な書き方ができるようになると思う。