Makefileでシェルコマンドの操作を楽に実行する

Blog Single

GitHubのリポジトリなどで、Makefile というファイルが置かれているのを見たことはないでしょうか?
Makefileというのは、makeコマンドで実行する処理を記述したファイルで、そのMakefileは書き方のルールをある程度知らないと何を書いているのか分からないため、一見とっつきにくく感じるかもしれませんが、基本的なルールさえ知ってしまえば、それほど難しくないので、今回はMakefileの簡単な使い方を紹介していきます。

makeコマンドとは

makeは、基本的にはプログラムのビルド作業を自動化するコマンドで、一部のファイルの変更で全てのファイルをコンパイルし直すのは時間の無駄なため、ファイルの依存関係を記述することによって、変更したファイルに依存関係があるファイルだけをコンパイルすることによって、コンパイルの時間を減らしたりすることができ、ファイルのコンパイルが必要なC言語などの言語での開発などでよく使われています。

makeはコンパイルを楽に行うようにするビルドツールであるとともに、インストールなどの一連の処理をまとめたり、テストの実行を簡単に行えるようにしたりするタスクツールのような形としてもよく使われています。

例としては、Go言語で開発されているインフラの管理をコードベースで行うためのツールであるTerraformの開発でもMakefileが利用されています。
Terraformのリポジトリで利用されているMakefileでは、開発で必要なツールのインストールの処理、コードの整形、シェルスクリプトの実行などの処理が記述されています。

何が便利か

業務などの普段の作業でシェルのコマンドを打つ際、オプションが複雑になるような操作は、コピペなどを行うことも多いかと思います。

makefileを利用することによって、そういったよく行う一連の処理を簡単に実行できたり、開発する上で実行する必要な一連の操作を他の開発者とも簡単に共有できたりします。

元々はビルドツールとして開発されたものですが、実際にはタスクツールとして利用されていることも多いため、今回は主にタスクツールとして利用方法について紹介します。

実行環境

GNU Makeがインストールされている環境を前提とします。

GNU MakeはMacOSやLinux系のOSであれば基本的にインストールされていると思います。

makeコマンドが使えるかどうかの確認

make -v

使い方

まずサンプルとして以下のようなGo言語のファイルを生成します。

main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello world")
}

これに対して、ビルドを行なってバイナリをファイルを生成する操作をMakefileに記載します。

Makefile

# タスク名や生成されるファイル
build:
    # 実際に行う操作 ビルドを行いmybinaryファイルを生成
    go build -o mybinary -v

書き方としては、以下のような構造で記述します。

<ターゲット名(タスク名や生成されるファイル名)>: <依存するタスクやファイル名>
    <実行処理>

実際に行う処理の前の空白は、空白文字ではなくタブ文字でないとエラーになるので注意が必要です。#から行末までがコメントです。

実行

$ make build

実行すると、記載したコマンドの操作が実行されバイナリファイルが生成されました。

makeコマンドの後ろにbuild(ターゲット名)をつけて指定しましたが、makeのみで実行した場合、Makefileの一番上のターゲットの処理が実行されるため、以下の実行でもmake buildと同じ処理が実行されます。

$ make

次に、テストの実行処理を記載します。

まずテスト用のサンプルファイルを用意します。

sum.go

package main

func sum(num1 int, num2 int) int {
    return num1 + num2
}

テストコード
sum_test.go

package main

import (
    "testing"
)

func TestSum(t *testing.T) {
    result := sum(3, 7)
    if result != 10 {
        t.Fatalf("failed test")
    }
}

Makefileに以下のコードを追加。

test:
    go test -v ./...

実行

$ make test

実行結果

go test -v ./...
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      _/Users/Username/filepath   0.009s

次に、先ほど追加した、テストを実行と、ビルドの実行の一連の処理を行うような処理を追加したい場合は以下のように行います。

Makefileに以下のコードを追加。

# 右側にターゲットに依存するタスクを記載
all: test build

実行

$ make all

こうすると、testで宣言した処理を行なった後、buildの処理が実行されるという処理が実行されます。

補足

今回は、ターゲットにファイル名ではなくタスク名を宣言しましたが、このような使い方をする場合、ターゲット名が生成物としてのファイル名なのかタスク名なのか分からなくなくなるため、擬似ターゲット(PHONY)として宣言するべきだったり、マクロといった変数のような機能もありますが今回は割愛しました。

まとめ

コマンドを毎回入力することによって、コマンドの操作が手で馴染んできたりするのはいいことではありますが、Makefileを利用するによってコマンドの長い処理が簡単に実行できたり、実行する必要のあるコマンドの処理の共有がしやすくなったりするなどのメリットがあります。

実際にプロジェクトに導入したりしなかったとしても、GitHubなどのリポジトリのMakefileを見るだけで、どういうツールを使っているのかや、シェルの便利な操作などを知ることができたりするので、是非Makefileを覗いてみたりしてください。

参考

Posted by MatsumotoKazuki
PHPやJavaで開発を行っているエンジニア。 LOLというゲームの試合を観戦するのが好きです。

Other Posts: