巨大なプロジェクト(Androidなど)をコンパイルするときに欠かせないccacheというツールがあります。
簡単に説明すると、過去にコンパイルした結果をキャッシュデータとして保存しておき、一致する場合はコンパイルをスキップして、結果をキャッシュデータから引き出してくるツールです。
使い方は大きく分けて2つあって、1つは環境変数やMakefileなどを書き換えてコンパイラの名前を変更する方法です。
例えば今までgcc hoge.cとしていたところをccache gcc hoge.cと書き換えたり、makeとしていた部分をCC='ccache gcc' makeとします。簡単ですが透過性が無いのが欠点で、そこらじゅうのMakefileを変えて回るのは非常に大変だろうことは、容易に想像できるかと思います。
もう1つはコンパイラの起動をフックする方法です。ccacheはシンボリックリンク経由で起動された場合、シンボリックリンクの名前に該当するコンパイラを探して起動する、という動作をします。やることとしては、
例えば /usr/bin/gccのコンパイル結果をキャッシュするなら…、
$ which gcc /usr/bin/gcc $ ln -s /usr/bin/ccache ~/bin/gcc $ export PATH=~/bin:$PATH $ which gcc /home/katsuhiro/bin/gcc
このようにします。またccache -sでどれくらいキャッシュが効いているかを見ることができますので、実際キャッシュ出来ているかどうかを見てみます。
$ echo 'int main;' > a.c $ ccache -s cache directory /home/katsuhiro/.ccache cache hit (direct) 0 cache hit (preprocessed) 0 cache miss 0 files in cache 0 cache size 0 Kbytes max cache size 1.0 Gbytes $ gcc -Wall a.c -c -o a.o a.c:1:5: warning: ‘main’ is usually a function [-Wmain] int main; ^ $ ccache -s cache directory /home/katsuhiro/.ccache cache hit (direct) 0 cache hit (preprocessed) 0 cache miss 1★★キャシュから結果を返せなかった★★ files in cache 3 cache size 12 Kbytes max cache size 1.0 Gbytes $ gcc -Wall a.c -c -o a.o a.c:1:5: warning: ‘main’ is usually a function [-Wmain] int main; ^ $ ccache -s cache directory /home/katsuhiro/.ccache cache hit (direct) 1★★キャシュから結果を返せた★★ cache hit (preprocessed) 0 cache miss 1 files in cache 3 cache size 12 Kbytes max cache size 1.0 Gbytes
きちんと働いてくれていそうです。
で、今日の本題なんですが、会社でccacheが動かないというので相談を受けて見に行ったら、確かにPATHをどう設定しても「コンパイラが見つからない」というエラーが出ていました。
散々悩んで辿り着いた答えはCCACHE_PATH環境変数でした。man ccacheとすると、しっかり説明が載っています。
この名前だけ聞いて、ああ、あれね?とわかる方は、かなりccacheを使い慣れている方だと思います。恥ずかしながら、わたくし全く知りませんでした…。
先の節で説明した2つ目の方法でccacheを起動すると、ccacheはPATHに列挙されたディレクトリからコンパイラを探そうとします。
しかし実はこの挙動はCCACHE_PATHという環境変数により変えることができて、もしCCACHE_PATHという環境変数が定義されていた場合、ccacheはPATHの代わりにCCACHE_PATHに列挙されたディレクトリからコンパイラを探そうとします。
相談されたエラーは間違ってCCACHE_PATHが定義してしまい、さらにCCACHE_PATHで何もないディレクトリを指していたため、ccacheが「コンパイラが無いですねー?」とエラーを出していたのでした。
$ gcc gcc: fatal error: no input files compilation terminated. $ export CCACHE_PATH=/usr $ gcc ccache: FATAL: Could not find compiler "gcc" in PATH★★コンパイラが見つからないと言っている★★ $ unset CCACHE_PATH $ gcc gcc: fatal error: no input files compilation terminated.
わかっていれば、何だ、そんなこと…というレベルの話ですが、意外とハマって苦戦したので、思い出として書き残しておきます。
鼻と耳は繋がっているから、鼻にイヤホン挿したら聞こえるというからやってみたけども、何も聞こえませんでした。
ウォークマンを最大音量にすれば聞こえますが、鼻に刺しても挿さなくても聞こえる音量は変わりません。
鼻詰まってるからかな?と思って、鼻かんでからやってみたけどやっぱり聞こえません。
もしかして「うわ、あいつ本当にやってるよ、バーカバーカwww」的な冗談だったのかなあ??
メモ: 技術系?の話はFacebookから転記しておくことにした。
前々から感じていたのですが、この2者は非常に相性が悪いと思います…。
例えばgit cloneしてきたリポジトリを ./configure && makeとすると、
という訳の分からない挙動をすることがあります。これはgit clone時にMakefile.amなどのタイムスタンプが変化してしまい、makeが勘違いして、ファイルが更新されたよ!依存するファイルを再作成しなければ!というアクションを起こしてしまうせいです。
これがもしtarballで展開したコードであればtaball作成時のタイムスタンプが復元されますので、この現象は起きません(tarballの作成者がヘマしていなければ、ですが)。
スマートな解決方法は「makeがファイルの変化を検知する方法を変える」ことです。恐らくmakeがファイルの変化を検知したい理由はたった1つで、
ただこれだけです。タイムスタンプを使うのは手段の一つに過ぎず、2ファイル間の新旧を判別できれば、タイムスタンプでなくても構わないはずです。
この日記のもとになったFacebookのエントリでは「タイムスタンプではなく、ファイルシステムが持っているブロックのハッシュ値が良いんじゃないか?」というコメントをいただきました。
前回のmake起動時と、今回のmake起動時の全ファイルのハッシュ値を記録しておけば、前回と変化したかどうか?はわかるし、ハッシュ再計算のコストがやや心配ですが、ファイルシステムが持っている値などを使えば抑えられる気がします。後は2ファイル間の順序関係を知る方法があれば、タイムスタンプの代わりになり得ると思います。
しかし、こんなの誰でも考え付きそうな話ですが、既に作られていたりしませんかねー…?
とはいえ、現状ではmake以外の選択肢がありません。その場しのぎではありますが、力ずくで解決してみようと思います。
お題はgit cloneした後などタイムスタンプがメチャクチャになった状態でも、autotoolsが再実行されないようにするには、どうすれば良いか?です。
まずはautotoolsってそもそも何なのか?を調べてみます。適当にautotoolsを使っているプロジェクトを持ってきて、autoreconfを実行したときの動きを見ます。環境はDebian 8.0 (Jessie, i386) です。
$ autoreconf --force -v 2>&1 | egrep ^autoreconf autoreconf2.50: Entering directory `.' autoreconf2.50: configure.ac: not using Gettext autoreconf2.50: running: aclocal --force★★こいつ★★ autoreconf2.50: configure.ac: tracing autoreconf2.50: configure.ac: adding subdirectory component/empty to autoreconf autoreconf2.50: Entering directory `component/empty' autoreconf2.50: configure.ac: not running libtoolize: --install not given autoreconf2.50: running: /usr/bin/autoconf --force★★こいつ★★ autoreconf2.50: running: /usr/bin/autoheader --force★★こいつ★★ autoreconf2.50: running: automake --force-missing★★こいつ★★ autoreconf2.50: Leaving directory `component/empty' autoreconf2.50: Leaving directory `.'
結果を見た感じでは、実行されるツールは4つaclocal, autoconf, autoheader, automake です。
次にこれらのツールが再実行される仕組みを追うため、./configure実行後に生成されるMakefileを見てみます。
まずはaclocalから。
top_srcdir = .
srcdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
$(ACLOCAL_M4): $(am__aclocal_m4_deps)
$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
$(am__aclocal_m4_deps):
ルールによればタイムスタンプが(新しい)aclocal.m4 > configure.ac(古い)という関係であれば、aclocalは再実行されません。
ちなみにm4ディレクトリに追加の .m4ファイルを入れている場合はam__aclocal_m4_depsにm4ディレクトリ内の .m4ファイルが並びます。従ってaclocal.m4 > configure.ac, (追加の .m4ファイル) という関係になります。
続けてautoconfです。
top_srcdir = .
srcdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
$(top_srcdir)/configure: $(am__configure_deps)
$(am__cd) $(srcdir) && $(AUTOCONF)
ルールによればconfigure > configure.ac, aclocal.m4であれば、autoconfは再実行されません。
どんどん行きましょう。続けてautoheaderです。
top_srcdir = .
srcdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
$(srcdir)/config.h.in: $(am__configure_deps)
($(am__cd) $(top_srcdir) && $(AUTOHEADER))
rm -f stamp-h1
touch $@
最後にtouch $@ しているのが特徴的です。どうもautoheaderは生成した内容と、既にあるファイルの内容に差が無ければconfig.h.inを一切書き換えない、という妙な作りになっているらしく、このautoheader再実行ルールが適用されてもconfig.h.inのタイムスタンプが更新されない場合があります。
もしタイムスタンプが更新されないとmakeは毎回このautoheader再実行ルールを適用してしまいますので、無駄を避けるためにtouchしてconfig.h.inのタイムスタンプを強制的に更新し、次回以降のautoheader再実行を回避していると思われます。
他のツールは強制的に書き換えに行くんですが、なぜautoheaderだけ仕様が違うんだろう…??
ま、それはさておき、ルールによればconfig.h.in > configure.ac, aclocal.m4であれば、autoheaderは再実行されません。
最後にautomakeです。
top_srcdir = .
srcdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
$(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
&& exit 0; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --foreign Makefile
ルールによればMakefile.in > Makefile.am, configure.ac, aclocal.m4であれば、automakeは再実行されません。
今までのルールをまとめると、下記のようになります。
全部まとめるとタイムスタンプの時刻が(新しい)Makefile.in > Makefile.am, configure, config.h.in > aclocal.m4 > configure.ac(古い)であればautotoolは一切、再実行されない、と思われます。
だからどうしたら良いんだ!俺は忙しいんだぞ!!という超短気な人のため、autotoolsの怒りを避けるためのビルド用のシェルスクリプトも付けておきます。
#!/bin/sh
# Prevent the autotools running...
touch aclocal.m4
touch config.h.in
touch configure
touch Makefile.am
touch src/Makefile.am
### もし他のサブディレクトリにMakefile.amがあればそれも
### find -name Makefile.am | xargs touchでも良いかもしれない
touch Makefile.in
touch src/Makefile.in
### もし他のサブディレクトリにMakefile.inがあればそれも
### find -name Makefile.in | xargs touchでも良いかもしれない
# Build
./configure
make
本当にこの節しか読まない人に注意しておくと、このスクリプトは現在のautotoolsの実装に依存していますので、将来autotoolsの実装が変わると、動かなくなる可能性が非常に高いです。動かなくなっても泣かないでください。
こういうダーティーハックは個人的には面白いから好きですが、苦労の割には利益が無いと思いました。
数年もすればこの手のハックは動かなくなるので周りに迷惑ですし、後進の人がメンテしようにも意味不明で「シバくぞゴラァ!書いた奴出てこいやー!!」ってキレること請け合いです。
住んでいる賃貸アパートのガスが、都市ガス(大阪ガス株式会社)から、いわゆるプロパンガス(帝燃産業株式会社)に切り替わりました。
腐っても国道沿いですし、一応は都市部に分類される地域のはずですから、プロパンから都市ガスに切り替わるならまだしも、その逆というのはあまり聞いたことがないのですが…。
筑波に居た時はプロパンでしたが、ガス代が妙に高かった印象しかありません。今は3,000〜7,000円程度で済んでいるガス代が、来月以降どれほど跳ね上がるやら、恐ろしい限りです。
何となく都市ガスとプロパンガスって呼んでますが、そもそも何が違うのか?が気になったので、調べてみました。
都市ガスは液化天然ガス(LNG, Liquefied Nature Gas)といってメタンを主成分(90%以上がメタン)とするガスです。メタン100%だと火力が弱いので、ブタンやプロパンを後から足して、火力を強くするそうです。
我が家に来ていた都市ガスは13Aという、一番発熱量が高く(13)一番燃焼が遅い(A)タイプです。1m^3のガスを燃焼させると45MJの熱量が得られます。
参考: 東京ガス: ガスご利用ガイド/都市ガスの熱量・圧力・成分
ガス器具に「プロパン用」「13A用」などと書いてある理由は、この発熱量と燃焼速度にあるそうです。例えば13A用のガス器具に想定していない燃焼速度のガス(例えば6Cなど)を使うと、燃えるのが速すぎて、本来ガスが燃えるべきではない場所で燃えてしまい、器具の異常加熱や事故に繋がります。
一方のプロパンガスは液化石油ガス(LPG, Liquefied Petroleum Gas)といってプロパンを主成分としたブタンとの混合ガスです。プロパン100%ではないので、プロパンガスという呼び方は本来正しくないようですが、何を今更…な感があります。
我が家に来ているLPGは家庭用なので恐らく「い号液化石油ガス」という、プロパンの含有量が最も高いタイプです。1m^3のガスを燃焼させると約100MJの熱量が得られますので、単純な火力としては都市ガスよりも上です。でも単価が高いから相殺されます。
参考: 日本LPガス協会: LPガスの概要: LPガスの規格
参考: 経済産業省 〜LPガスを安全に使うために、LPガスの基礎知識〜
ライブラリの守備範囲は狭い方がいい - Konifar's WIP を読んで。
リンク先の記事に同意です。可能な限り1つのモジュールは1つの課題にフォーカスし、規模もできるだけ小さくすべきだと思います。
システムは人が作るものですから、人と同様「より長く、より広く生きて(=使って)ほしい」という思いが根底にあるはずで「時代に合わせて変われないものから滅びる」という生物の摂理には逆らえないだろう、という考えでいます。
モジュールのフォーカスを狭めたい理由として「開発の参入障壁を下げて、変更のチャンスを増やす」「規模を小さくして、変更コストを下げる」を挙げたいです。
何でそうしなきゃいけないと思うか?は人によるし、正解も間違いも無いと思いますので、他の方の想いも聞いてみたいところですねー。
メモ: 技術系の話はFacebookから転記しておくことにした。
目次: Kindle
今だけなのか昔からなのかわかりませんが、Amazonポイントを使って購入すると、注文確認メールに</tr>と入ったメールが来ます…。何これ。通常の買い物では特に異常はないです。
商品の小計: ¥514 Amazonポイント: -¥86 </tr> ........................ 注文合計: ¥428
仕事でもプライベートでも、自身の行いや意見に「それは○○の理由でだめ、△△すべき。」のように批判もしくは評論されることがあると思います。
昔は意見の扱いに困っていましたが、最近は、なるほど正論だな〜と思ったら、
「ご意見ごもっともです。では一緒にやりましょう。私は(半分くらい)やります、あなたは(半分くらい)をやっていただけませんか?」
と返すようにしています。
というのは、この後「やる」か「やらない」か?を見れば、その意見が「批判+提案」なのか「評論」なのかが、割とキッパリと分けられる気がするからです。
この法則が合っているかどうか、しばらくこの返しを続けてみようと思います。
メモ: 技術系の話はFacebookから転記しておくことにした。
IntelliJ IDEA 14のエディタにはコードを選択してCtrl+Alt+Iを押すと、自動的にインデントを調整してくれる機能があります。この機能、Javaのコードスタイルに合わせてインデントを調整するので、基本的には文句のない結果になります。
しかしながら、個人的に1点だけ気に入らない点があります。何かと言うとswitch文の内部にあるcaseに余計なインデントが付くことです。例を挙げると、デフォルトでは下記のようにインデントしてくれます。
switch (a) {
case 0:
doCase0();
break;
default:
doDefault();
}
本当は下記のように、switchとcaseの位置が揃ってほしいのです。
switch (a) {
case 0:
doCase0();
break;
default:
doDefault();
}
この程度、設定(※)で何とかなるだろ?と思ったら、意外にもswitch文に関する設定がありませんでした。困った。
(※)IntelliJ IDEA 14の自動インデントの設定は、メニューのFile - Settingsを選び、左側のツリー表示からEditor - Code Style - Javaにあります。種別としてはIndentに相当するはずですが、switch文について言及されている項目は1つもありません。
インデントの違いは非常に些細なことですが…、個人的に見た目が受け付けないのと、今まで書いてきたコードのインデントがことごとく変わり、バージョン管理システムが差分を大量に表示するので、うっとおしいのです。
前述のようにGUIから設定する方法はなさそうなので、ひとまずGUIからの設定は諦めました。代わりに自動インデントの設定ファイルを直接書き換えようと思います。
まず、自動インデントの設定(メニューのFile - Settings、左側のツリー表示からEditor - Code Style - Java)を適当に書き換え、適当な名前、例えばDefault(1) という名前で保存します。するとC:\Users\username\.IdeaIC14\config\codestyles\Default _1_.xmlという設定ファイルができます。
その後、起動しているIntelliJ IDEA 14を全て終了させて、Default _1_.xmlの設定を直接書き換えます。下記の★部分を追加してください。
<code_scheme name="ConfigName">
...
★ <codeStyleSettings language="JAVA">
★ <option name="INDENT_CASE_FROM_SWITCH" value="false" />
★ </codeStyleSettings>
</code_scheme>
設定を書き換えたら、IntelliJを再び起動してください。するとswitch - case文の内部が自動インデントされなくなります。
週刊アスキー - Surface 3や薄型ノートPCをわずか1万円強で超強化する棒型ドック、その名も“ピッコロ”を読んで。
USB 2.0の帯域だとFull HDの外部ディスプレイだけで一杯一杯でしたが、USB 3.0の帯域ならWQXGAの外部ディスプレイ+外付けストレージx 2でも余裕なんですねー。
メモ: 技術系の話はFacebookから転記しておくことにした。