Makefileの変数展開はレシピの実行前に行われる

makeなんてよく使うものだから分かっているつもりだったけど実はよく分かっていなかったのが、変数展開がどのタイミングで行われるかということ。

itchyny.hatenablog.com

Makefileでの := は simply expanded variable といって一度しか展開されないが、 = は参照するたびに展開される。

DATE = $(shell date)

.PHONY: all
all:
   @echo $(DATE)
   @$(shell sleep 3)
   @echo $(DATE)
   @$(shell sleep 3)
   @echo $(DATE)

これは、 $(DATE) を参照するたびに展開されるから、値はどんどん変わっていく。

Thu Apr 4 20:13:21 JST 2019
Thu Apr 4 20:13:24 JST 2019
Thu Apr 4 20:13:27 JST 2019

ここまでは知っていたのだけど、どのタイミングで展開されるのかというのを知らなくて、次のようなケースで悩んでしまった。

FILE := /tmp/example.txt
$(shell echo 'Old contents' > $(FILE))
CONTENTS = $(shell cat $(FILE))

.PHONY: all
all:
  echo $(CONTENTS)
  echo 'New contents' > $(FILE)
  echo $(CONTENTS)

ファイルに書き込んだ後に $(CONTENTS) を参照しているから、最後の行は New contents と表示する… わけではない。

echo Old contents
Old contents
echo 'New contents' > /tmp/example.txt
echo Old contents
Old contents

こうなる理由だが、Makefile$(...) の展開はレシピを実行する前に行われるからだ。上の例だと、レシピ内のすべての $(CONTENTS)$(FILE) が展開されてから、3つのコマンド実行が行われる。

次の例を考えてみよう。

FILE := /tmp/sample.txt

.PHONY: all
all:
   @echo $(shell date)
   @$(shell sleep 3)
   @date > $(FILE)
   @$(shell sleep 3)
   @echo $(shell date)
   @$(shell sleep 3)
   @cat $(FILE)

レシピ3行目の date > $(FILE) (正確にはすでに展開されているので date > /tmp/sample.txt) が実行されるのは、全ての sleep (2, 4, 6行目) が行われた後なので、ファイルに書かれた日付が最も (5行目の echo $(shell date) よりも) 新しくなる。

では、ファイル書き込みがあってその最新の内容を取りたいときはどうすればいいか。レシピが実行される前に展開されても、まだ cat は実行されない状態にするには次のようにすれば良い。

FILE := /tmp/example.txt
$(shell echo 'Old contents' > $(FILE))
CONTENTS = $$(cat $(FILE)) # instead of $(shell cat $(FILE))

.PHONY: all
all:
  echo $(CONTENTS)
  echo 'New contents' > $(FILE)
  echo $(CONTENTS)

そう、シェルの展開に変えてしまうのだ。 (もうこうなると := を使っても同じになる)

echo $(cat /tmp/example.txt)
Old contents
echo 'New contents' > /tmp/example.txt
echo $(cat /tmp/example.txt)
New contents

多くの場合は $(shell ...) が使えるのでこれを使っていたが、これが使えないケースがあることに気がついて少しワクワクした。

stackoverflow.com めっちゃ混乱してstackoverflowに投げたら解決した。

GNU Make 第3版

GNU Make 第3版

joのGo実装 gojo を作りました!

joというJSONを組み立てるコマンドがあって、これは2016年からある便利なCLIツールなのですが、昨日急に思い立ってGo実装を作りました。

go get -u github.com/itchyny/gojo/cmd/gojo

brewでもインストールできます。

brew install itchyny/tap/gojo

使い方はこんな感じ。

 $ gojo foo=bar qux=quux
{"foo":"bar","qux":"quux"}
 $ gojo -p foo=bar qux=quux
{
  "foo": "bar",
  "qux": "quux"
}
 $ gojo -a foo bar baz
["foo","bar","baz"]
 $ seq 10 | gojo -a
[1,2,3,4,5,6,7,8,9,10]
 $ gojo -p foo=$(gojo bar=$(gojo baz=100))
{
  "foo": {
    "bar": {
      "baz": 100
    }
  }
}
 $ curl -s https://httpbin.org/post -X POST \
    -H 'Content-Type: application/json' \
    -d "$(gojo foo=bar baz=$(gojo qux=128))" | jq .json
{
  "baz": {
    "qux": 128
  },
  "foo": "bar"
}

joはそれ自身と組み合わせて深いJSONを作るという考え方がとてもかわいくていいですね。

必要な機能はそんなに多くないので、思い立って三時間ほどで概ね実装できました。jo自体はあまり便利かどうか分からず使ったことはない*1のですが、自分で作ってみると意外と使い所があるかもと思っています。

GoのJSONは順序が失われてしまうので、順序を保存するためにorderedmapというライブラリーを使っています (が、若干バグを見つけたので報告しました)。

小さいツールなのでそこまで大したことはやってませんが、Go modulesやFunctional option patternを使ったりして今どきのGoの構成になっていると思うので、参考にしていただけると嬉しいです。

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

*1:実はjoのリリース当初にcontributeはしたことがあるんですが、正直言うと作者の設計方針が好きになれなかった

gore 0.4.0をリリースしました!

Go言語のREPL、goreの0.4.0をリリースしました。

id:motemenさんに連絡をとって、goreのコミット権をいただきました。 最初はpull requestが溜まっていたので片付けて、細かいバグ修正などを行いました。 しばらく触っていると慣れてきたので、新機能も実装して入れました。

かなり便利になっているので、ぜひアップデートして (またはインストールして) お使いください。

go get -u github.com/motemen/gore/cmd/gore

バグ修正

  • 特定のケースで Evaluated but not used というエラーが出ることがあるのを修正しました
    • 例えばlen(fmt.Sprint(1)) を二回評価すると出ていた
  • おかしなトークンが入力されたらエラーを表示するようにしました
    • 例えば foo # bar と入力すると invalid token: "#" と表示します

新機能

  • 関数定義をサポートしました
    • これまでも f := func(x int) int { return x * 2 } と書くことはできましたが、今回のアップデートで func f(x int) int { return x * 2 } もサポートしました
  • :type コマンドで変数や関数の型を確認できるようにしました (ghciと同じような感じです)
  • :clear コマンドで定義した変数や関数を削除できるようにしました
  • コマンドの省略形をサポートしました
    • 例: :import に対して :i, :type に対して :t, :quit に対して :q など

その他の改善

  • Go 1.12をサポートしました
    • gocodeとgodocのアップデートもおすすめします
  • Makefileを追加しました
  • mainパッケージをcmd/gore/に移動しました
  • 起動時のヘッダーを標準エラー出力に出すようにしました (pythonと同じです)
  • 一時ディレクトリーの改善 (ディレクトリー名にgore-をつける、終了時に削除する)
  • コンパイルエラーが起きたときのメッセージ改善 (一時ファイルの名前を表示しないなど)
  • 他にも細かい改善を行いました

gore 0.4.0のスクリーンショットです。 f:id:itchyny:20190225215651p:plain

バグ報告は Issues · motemen/gore · GitHub までお寄せください。より良いGo-lifeのために、今後も開発を進めていきます。

2018年を振り返って

今年は仕事を淡々とこなしつつ、自分の技術の方向性に悩みながらも、ずいぶんとだらけてしまった一年だったと思います。技術面での成長に伸び悩んでいます。

Mackerelのコードの整理や改善は無限にやることがあるのですが、平日夜や休日をそれで潰す生活をしていると、頭の切り替えがうまく行かなくなり仕事中に集中できなくなってしまいました。フロントエンドはかなりコードの整理が進み、SPA化できたのはよかったですね。コンテナ周りはチョットワカルと言えるようになりたいですね。

春先にバイナリエディタをリリースしました。まだ実装したい機能はたくさんありますが、リリースしたら燃え尽きてしまってあまりコードを触れていません。Goを書き続ける良い遊び場なので、今後もきちんと開発を続けていきたいですね。

バイナリエディタの後は特に何かを作ることもなく、また新しい技術を学ぶこともなく、のんびり過ごしてしまいました。以前は仕事から帰ってからも3〜4時間コードを書くことができましたが、夏頃に小指の痛みがひどくなり、夕食をとってからベッドでだらだら過ごす日が多くなりました。好きなYouTuberを見つけたのも今年のことでした。

プログラミング以外の趣味を持たなければ人生が薄っぺらいまま終わってしまうのではないかという危機感をいだき、12月に入ってから思い切ってピアノを購入しました。音楽は良いものですね。最近は毎日最低でも二時間は練習時間に取れていると思います。小学生の頃にピアノを習っていましたが、中学受験のために辞めて以来なので、実に十数年のブランクがあります。基礎練習が大事だというのは嫌というほど知っているので、単調な練習曲を5回10回と繰り返しながら、指を動かす感覚を取り戻しているところです。実家から教本を何冊かもらって帰るつもりです。ショパンが大好きなので弾けるようになるといいですね。

ウェザーニュースLiVEを頻繁に見るようになったのも今年に入ってからです。個性的なキャスターがみな魅力的で、番組の内容は聞き続けても飽きません。声も個性的なので今では聞くだけで誰かすぐわかるようになりました。SOLiVE24時代をリアルタイムに楽しめなかったのがすごく残念です。

ルービックキューブも今年に入ってから始めました。昔から日本配色のキューブを持っていましたが、会社で流行したタイミングでMoYuのGTS2Mを買いました。興味は次第に多次元・多面体キューブに移っていき、LanLanやYuXinのキューブを集めて遊んでいます。未だにGreg's Multi Cubeの解き方がわかっていません。commutatorとconjugateは回し方を整理するのには便利ですが、新しいキューブを解くのは私にはまだ難しいです。

Vim本体の最新パッチを追う情熱は持てなくなり辞めてしまいました。今年はプラグインも作れてないですね。最近入れたプラグインの中ではtraces.vimはかなり便利なプラグインだと思います。lightline.vimは3300スターを超えて、今もなお増え続けています。つい先日vim-winfixのバグを直したので、こういう小粒便利プラグインの紹介でも書いてみようかなと考えています。git系のプラグインはなかなか手になじまないことが多いので自分で作ろうかなと考え始めています。

今年もたくさんのアニメを見ました。中でも『多田くんは恋をしない』『はるかなレシーブ』『色づく世界の明日から』『やがて君になる』は印象に強く残っています。来年は『マギアレコード』『フルーツバスケット 新』『ストライクウィッチーズ 501』そして『PSYCHO-PASS 劇場版』に期待しています。

今後もブラウザ、言語処理系、機械学習という三分野を深掘りしていきたいという気持ちは変わっていません。仕事と趣味そして休息のバランスを取りながら来年も頑張っていきましょう。

風野あさぎ「あいかわらずが続くのって、つらくないですか」

色づく世界の明日から 第八話

Mackerelのグラフを端末で描画するコマンドmkrgを作りました

Mackerelのグラフを端末で見れたらいいなと思ったので作ってみました。

github.com

使い方

 $ go get -u github.com/itchyny/mkrg/cmd/mkrg
 $ mkrg

コマンドを叩くと、そのホストのメトリックを取ってきてグラフを表示します。 f:id:itchyny:20181014233825p:plain 何も考えずにコマンドを叩けば、システムメトリックのグラフを表示してくれます。 私はiTerm2を使っているので、とりあえずiTerm2では画像を表示できるようにしています。それ以外の端末では点字を使って頑張って表示します。 f:id:itchyny:20181017235153p:plain

Mackerel サーバ監視[実践]入門

Mackerel サーバ監視[実践]入門

似たような目的のコマンドとしては、同僚の作ったmkr-graphというコマンドがあります。 medium.com

実装

Mackerelは各種APIを揃えていて、中でもメトリックを取得するAPIを使うと、時系列データを引くことができます。 Mackerelのweb画面を見ながら必要なメトリックを調べて、データを引っ張ってきて、グラフに表示すれば完成です。

iTerm2での画像の表示は以下のページを見れば分かります。画像データをbase64で出力するだけなので、とても簡単です。 www.iterm2.com

一応Sixel用の実装も用意してますが動作は見てません。

点字文字コードですが、Braille Patterns - Wikipediaを読めばわかるように、\u2800をベースにして各点に対してビットを立てれば狙ったパターンの文字コードを得ることができます。ユニコードできちんと並んでいて助かります。

言語はGoを使っています。便利な言語です。

みんなのGo言語[現場で使える実践テクニック]

みんなのGo言語[現場で使える実践テクニック]

今後

一昨日から作りはじめてシュッと動いてくれてわりと満足しています。カスタムメトリックのグラフやロールグラフ、サービスメトリックのグラフなんかも表示できると楽しいかもしれません。

個人的には、iTerm2の画像を表示するAPIがすごい単純だということがわかったのが一番の収穫でした。

宣伝

はてなではシステムのメトリクスを見るのが好きなエンジニアを募集しています。