GNU Make
Linux系ソフトウエアの多くは、ビルドシステムとして「GNU Make」か、もしくはこの仕組みに準拠したMakefileを生成するツールなどで構成されていることが多い。Linux系のソフトウエア以外でもGNU Makeをビルドツールとして使っているものは多々存在する。
GNU Makeはmakeの実装系の一つであり、基本的なmakeの機能を拡張したものだ。makeは汎用的なツールであり、対象となるプログラミング言語を限定しない。もちろん、これはGNU Makeも同様だ。
このビルドツールの主役となるのは、「make」というコマンドと、「Makefile」というビルドの方法をまとめたファイルの2つだ。makeコマンドを通じて「make ターゲット」といった操作を行い、Makefileに記載されたビルド処理を行うというのが主な利用方法となっている。
Makefileにはいくつかの書き方があるのだが、アイデアの根幹となっているのは「基となるファイル」「基ファイルから生成されるファイル」という2つのコンビネーションを指定し、さらに「生成されるファイルを生成するための方法」を書く、というものだ。次のような書き方をする。
基ファイルから生成されるファイル: 基となるファイル
生成されるファイルを生成するための方法
この「依存関係とビルド方法」は1つだけではなく、次のように複数指定することができる。
基ファイルから生成されるファイル1: 基となるファイル1
生成されるファイル1を生成するための方法
基ファイルから生成されるファイル2: 基となるファイル2
生成されるファイル2を生成するための方法
基ファイルから生成されるファイル3: 基となるファイル3
生成されるファイル3を生成するための方法
基ファイルから生成されるファイル4: 基となるファイル4
生成されるファイル4を生成するための方法
基ファイルから生成されるファイル5: 基となるファイル5
生成されるファイル5を生成するための方法
...
生成されるファイルがほかのファイルの基ファイルになっていても良い。2つのファイルから1つのファイルが生成されていても良いし、3つのファイルから1つのファイルが生成されていても良い。こんな感じで依存関係とビルド方法をリストに書いておくと、あるファイルを得るために必要になるファイルの依存関係とその生成方法が綺麗に整理できる。
あらかじめ整理しておくことで、あるファイルを書き換えたときに、影響があるファイルだけをビルドする、といったことが可能になる。
例えば、trコマンドやcatコマンドのように短いソースコードのソフトウエアであれば、こういった工夫は必要ない。だが、LinuxカーネルやGoogle Chromeのように、規模の大きなソフトウエアの場合、必要な部分だけをビルドするようにしないと、ファイル書き換え時にいちいち全体をビルドすることになり、時間がかかりすぎて実用的なソフトウエア開発ができなくなってくる。必要最小限のビルド作業を実現できるのがmakeであり、makeはソフトウエア開発には欠かすことのできないツールなのである。
現在はこうした仕組みを提供しているのはmakeだけではなく、プログラミング言語や統合開発環境ごとに専用の方法が用意されていることも多い。表現方法は異なるものの、基本的な考え方は、どれもmakeとよく似ている。
このように、Linuxでソフトウエア開発を行ったりする場合、makeは必要になるが、Linuxを管理する際には特に必須というわけではない。ただし、makeはビルド以外にも使える便利なツールだ。以降では、開発者以外にも役立つmakeの使い方を紹介しよう。
作業の整理にmakeを使う
makeは、その動作内容をMakefileというファイルにまとめておくため、このファイル自体を作業の整理に使うことができる。Makefileの基本的な使い方は、ファイルの依存関係とビルド方法を書いておくことなのだが、実はファイルの依存関係は記述を省略してもよいため、操作を整理する目的で使うこともできる。
例えば、業務ごとに作業するディレクトリを設け、そこにMakefileを用意するというやり方が考えられる。このMakefileに、その業務で使うコマンドや操作をまとめておくわけだ。
また、次のMakefileは筆者が使っているものの一つだが、ある業務の特定の作業をMakefileにまとめたものになっている(実際には分割してるので実体はもっと大きいのだが、サンプルとしてご覧いただきたい)。
Makefileのサンプル
# Copyright (c) 2005-2007,2009,2010,2021 ONGS Inc.
# All rights reserved.
#
# This software may be used, modified, copied, and distributed, in
# both source and binary form provided that the above copyright and
# these terms are retained. Under no circumstances is the author
# responsible for the proper functioning of this software, nor does
# the author assume any responsibility for damages incurred with its
# use.
# author: Daichi GOTO (daichi@ongs.co.jp)
# first edition: Fri Nov 4 19:26:33 2005
# last modified: $Date: 2007-07-26 07:33:41 $
# version: $Revision: 1.8 $
.include "mk/vars.mk"
.include "mk/apps.mk"
all: pull
#=========================================================================
# 新規記事作成
#=========================================================================
add:
@LANG=C; \
if [ "" = "${name}" ]; \
then \
${MAKE} add name=$$(date +%Y%m%d); \
false; \
fi
@mkdir ${name}
@cat template/Makefile | \
sed -e "s/USER/${USER_FULLNAME_ROMA} (${USER_EMAILADDRESS})/" \
-e "s/DATE/\$$$$(${ECHO} Date)\$$/" \
-e "s/REVISION/\$$$$(${ECHO} Revision)\$$/" \
> ${name}/Makefile
@cat template/typescript.xml | \
sed -e "s/USER/${USER_FULLNAME_KANJI}/" \
-e "s/DATE/\$$$$(${ECHO} Date)\$$/" \
-e "s/REVISION/\$$$$(${ECHO} Revision)\$$/" \
-e "s/NOW/$$(LANG=C date)/" \
> ${name}/typescript.xml
mkdir -p ${name}/images
touch ${name}/images/001raw.jpg
touch -r ${name}/images ${name}/images/001raw.jpg
# If the modification time of the images directory and the files under it
# are all the same, the default image is assumed to be used.
@cd ${name}; \
${MAKE} ; \
${MAKE} build; \
${MAKE} view-yd; \
${MAKE} view; \
${MAKE} edit; \
${MAKE} clean;
@sleep 0.5
@${ECHO}
@LANC=C; \
ls -d "${YEAR}"* | grep --mmap -E "${TYPESCRIPT_DIRPATTERN}" | \
while read target; \
do \
if [ -e "$${target}/${TYPESCRIPT_TMPCHECK_FILE}" ]; \
then \
${ECHO} "HEADS UP: $${target} under working!"; \
fi; \
done;
#=========================================================================
# 操作系ターゲット
#=========================================================================
pull:
make clean
git pull
clean:
for dir in $$(ls | grep -E "${TYPESCRIPT_DIRPATTERN}"); \
do \
cd $$dir; \
${MAKE} clean; \
cd ..; \
done
view:
for dir in $$(ls | grep --mmap -- ${name}); \
do \
cd $$dir; \
${MAKE} ; \
${MAKE} view; \
cd .. ; \
done
似たような業務を行うときは、新しくディレクトリを作成してMakefileはコピーして使い回す。こうすることでデータは業務ごとに変えつつ、ロジックそのものは使い回すことができる。業務ごとにちょっとずつ違う部分が出てくるので、そのときはコピーしたMakefileを書き換える。
同じことはスクリプトにまとめてもできるのだが、Makefileを使う方法と併用すると良いと思う。使い分け方としては、Makefileにまとめるようなものは特定の仕事や業務に特化したもの、スクリプトファイルにまとめるのはそうした業務や作業で横断的に使うような汎用的なもの、としておくと良いだろう。
makeコマンドというインタフェース
Makefileにはファイルの依存関係ではなく、次のような書き方をしておくこともできる。 ターゲット1: コマンドなどの処理1
ターゲット2:
コマンドなどの処理2
ターゲット3:
コマンドなどの処理3
ターゲット4:
コマンドなどの処理4
ターゲット5:
コマンドなどの処理5
具体的な処理で記述してみると、次のような感じだ。
edit:
編集処理
list:
一覧表示
update:
更新処理
clean:
不要データの削除処理
こうした書き方をした場合、「make edit」や「make list」、「make update」といったコマンドを実行することで、そこに書いてある処理が実行されるようになる。操作に慣れてくると、作業する際、自動的に「make ○○」といったタイピングをするように体が覚える。
実際に実行されるコマンドはMakefileに書いてある内容だ。そのため、例えば同じ「make edit」というコマンドを実行したとしても、あるディレクトリではVimが実行され、あるディレクトリではVisual Studio Codeが実行され、あるディレクトリでは専用アプリケーションが実行される……といったように処理を変えられる。しかし、編集するという行為に対して最初に入力するアクションは「make edit」として共通化できる。これが、なかなか便利なのである。
Makefileというドキュメント
業務によっては、年に1、2回しか行わないとか、それこそ数年に1回しか行わない作業というものもあるだろう。そうした作業は、どの手順で何をすればよいのかを文書化しておかないと、忘れてしまう。そんなシーンでも、Makefileは役に立つ。
Makefileの基本的な書き方はシンプルだ。「ファイルの依存関係と生成するコマンド」、または、「ターゲットと処理内容」といった組み合わせで記述する。このシンプルなフォーマットは、業務手順を書いておく方法としても役立つのだ。実際の処理内容を書いておけば自動化もできるため、一石二鳥である。
今回は、結構いろいろなシチュエーションで使えるmakeとMakefileについて取り上げた。いずれも、開発目的以外でも何かと役に立つツールだ。ぜひ活用していただきたい。