反復的なタスクを自動化するためのMakefileの使い方

この文章は、「はじめに」という手紙のご挨拶を示しています。

もしもあなたがLinuxサーバーにソースコードからソフトウェアをインストールする経験があるならば、おそらくmakeユーティリティに出くわしたことがあるでしょう。このツールは主にプログラムをコンパイルしてビルドするために使用されます。ソースコードの作者が、その特定のプロジェクトをビルドするために必要な手順を示すことを可能にします。

「make」はソフトウェアのコンパイルを自動化するために作成されましたが、このツールは柔軟に設計されており、コマンドラインから実行可能なほとんどのタスクを自動化することができます。このガイドでは、makeを使用して連続的に発生する繰り返しタスクを自動化する方法について説明します。

前提条件

このチュートリアルでは、どの種類のLinux環境でも問題ありません。パッケージのインストール手順は、Ubuntu/Debian LinuxとRed Hat/Rocky Linuxの両方に対応しています。

Makeをインストールする

ほとんどのLinuxディストリビューションでは、単一のコマンドでコンパイラをインストールすることができますが、デフォルトでは提供されていません。

Ubuntuでは、現代的でよくサポートされているコンパイラ環境に必要なすべてのパッケージを提供するbuild-essentialというパッケージをインストールすることができます。パッケージソースを更新し、aptを使ってパッケージをインストールしてください。

  1. sudo apt update
  2. sudo apt install build-essential

 

Rocky Linuxや他のRed Hatの派生版では、同じコンパイラ機能を提供する開発ツールのグループをインストールすることができます。dnfコマンドを使用してパッケージをインストールしてください。

  1. dnf groups mark install “Development Tools”
  2. dnf groupinstall “Development Tools”

 

システムにおいてメイクコマンドの存在を確認することで、コンパイラが利用可能かどうかを確認できます。それを行うためには、whichコマンドを使用してください。

  1. which make

 

Output

/usr/bin/make

今、通常の能力を持つメイクの恩恵を受けるために必要なツールを手に入れました。

メイクファイルの理解

makeコマンドが指示を受け取る主要な方法は、Makefileを使用することです。

メイクファイルはディレクトリごとに固有のものであり、makeコマンドはそのディレクトリ内でこれらのファイルを探します。私たちは実行するタスクに応じて、またはスクリプトを呼び出すのに最も適切な場所であるルートにメイクファイルを配置すべきです。

Makefile内では、特定の形式に従います。Makeは、ターゲット、ソース、およびコマンドの概念を以下のように使用します。

メイクファイル
target: source
    command

このものの配置と形式は非常に重要です。ここでは、各構成要素の形式と意味を話し合います。

ターゲット

対象は、ユーザーが指定した名前で、一連のコマンドに参照するためのものです。それをプログラミング言語の関数に似たものと考えてください。

対象は左側の列に配置され、連続した単語である(スペースなし)そしてコロン(:)で終わる。

Makeを呼び出す際、以下のようにターゲットを指定することができます。

  1. make target_name

 

その後、makeはMakefileをチェックし、そのターゲットに関連するコマンドを実行します。

ソースは一つだけ必要です。

ソースはファイルやその他の対象物への参照です。それらはターゲットに関連付けられた前提条件や依存関係を表します。

例えば、Makefileのセクションで以下のようになることがあります:

メイクファイル (Meiku Fairu)
target1: target2
    target1_command

target2:
    target2_command

この例では、target1というように呼ぶことができます。

  1. make target1

 

それから、MakeはMakefileに移り、target1というターゲットを検索します。次に、指定されたソースがあるかどうかを確認します。

それは、target2のソース依存関係を見つけ、一時的にそのターゲットに移動します。

そこから、target2にリストされたソースがあるかどうかを確認します。ソースがない場合は、target2_commandを実行します。この時点で、makeはtarget2のコマンドリストの最後に到達し、制御をtarget1に戻します。そして、target1_commandを実行し、終了します。

ソースはファイルまたは対象そのものである場合があります。Make はファイルのタイムスタンプを使用して、前回の呼び出しからの変更があったかどうかを確認します。ソースファイルに変更が加えられた場合は、そのターゲットを再実行します。それ以外の場合は、依存関係を満たしたとマークし、次のソースに進みます。または、そのソースが唯一の場合は、コマンドに進みます。

一般的な考え方は、ソースを追加することによって、現在の対象の前に実行される必要がある依存関係の連続セットを構築できるということです。対象の後には、スペースで区切られた複数のソースを指定することができます。こうして、複雑なタスクのシーケンスを指定する方法が見えてきます。

命令

makeコマンドが柔軟性を持つ理由は、その構文のコマンド部分が非常にオープンエンドであることです。ターゲットの下で実行するコマンドを任意に指定することができます。必要なだけコマンドを追加することができます。

ターゲット宣言の後の行にコマンドを指定します。コマンドはタブ文字で1つインデントします。makeのバージョンによっては、コマンドセクションのインデント方法に柔軟性があるものもありますが、一般的には、意図が認識されるようにするために、単一のタブを使用することが望ましいです。

Makeは、ターゲットの定義の下にある各インデントされた行を個別のコマンドとみなします。インデントされた行やコマンドは、好きなだけ追加することができます。Makeはそれらを一つずつ処理します。

コマンドの前に配置することで、異なる処理方法を指定するいくつかの方法があります。

  • -: A dash before a command tells make to not abort if an error is encountered. For instance, this could be useful if you want to execute a command on a file if it is present, and do nothing if it is not.
  • @: If you lead a command with the @ symbol, the command call itself will not be printed to standard output. This is mainly used just to clean up the output that make produces.

追加の機能 (Tsuijō no kinō)

追加機能を利用することで、Makefile内でより複雑なルールチェーンを作成することができます。

変数

Makeは変数(またはマクロ)を認識し、これらはmakefile内の置換物として機能します。これらをファイルの先頭で宣言することが最適です。

各変数の名前は完全に大文字で表記されます。名前の後にイコール記号があり、右側の値に名前が割り当てられます。例えば、インストールディレクトリを/usr/binと定義したい場合、ファイルの先頭にINSTALLDIR=/usr/binと追加することができます。

このファイルの後半では、$(INSTALLDIR) の構文を使って、この場所を参照することができます。

改行を回避

私たちが行えるもう一つの便利なことは、コマンドを複数行にまたがることを許可することです。

各コマンドセクションでは、任意のコマンドやシェル機能を使用することができます。これには改行文字を\で終わらせてエスケープすることも含まれます。

メイクファイル
target: source
    command1 arg1 arg2 arg3 arg4 \
    arg5 arg6

もしif-then文のようなシェルのプログラム機能を活用するなら、これはさらに重要になります。

メイクファイルを日本語で表現すると、以下のようになります:「メイクファイル」
target: source
    if [ "condition_1" == "condition_2" ];\
    then\
        command to execute;\
        another command;\
    else\
        alternative command;\
    fi

このブロックは、まるで1行のコマンドのように実行されます。実際、1行で書くこともできましたが、このように分割することで可読性がかなり向上します。

改行文字をエスケープする場合、バックスラッシュ(\)の後に余分なスペースやタブがないことを確認してください。そうでないとエラーが発生します。

ファイルの拡張子規則

ファイル処理に使える追加機能として、ファイルの接尾辞があります。これは、ファイルの拡張子に基づいてファイルを処理する方法を提供する一般的な規則です。

例えば、ImageMagickスイートを使用してディレクトリ内のすべての.jpgファイルを処理し、それらを.pngファイルに変換したい場合、Makefileには次のようなものがあります。

メイクファイル
.SUFFIXES: .jpg .png

.jpg.png:
    @echo converting $< to $@
    convert $< $@

ここで見るべきいくつかの事柄があります。

最初の部分は、.SUFFIXES: 宣言です。これにより、ファイルの接尾辞として使用するすべての接尾辞についてmakeに通知します。.cや.oなどのソースコードのコンパイルによく使用される接尾辞は、デフォルトで含まれており、この宣言でラベル付けする必要はありません。

次の部分は、実際の接尾辞ルールの宣言です。これは、original_extension.target_extension: の形式を取ります。

これは実際のターゲットではありませんが、2番目の拡張子を持つファイルの呼び出しに一致し、1番目の拡張子のファイルからそれらを作成します。

私たちの場合、ディレクトリにfile.jpgがある場合、私たちはmakeという呼び出しを使用してfile.pngというファイルを作成できます。

  1. make file.png

 

“.SUFFIXES宣言で.pngファイルを見つけ、.pngファイルを作成するためのルールを確認します。次に、ディレクトリ内で.pngを.jpgに置き換えたターゲットファイルを探します。その後、続くコマンドを実行します。”

サフィックスの規則では、まだ紹介されていないいくつかの変数を使用しています。それらは、現在のプロセスのどの部分に基づいて異なる情報を代替するために役立ちます。

  • $?: This variable contains the list of dependencies for the current target that are more recent than the target. These would be the targets that must be re-done before executing the commands under this target.
  • $@: This variable is the name of the current target. This allows us to reference the file you are trying to make, even though this rule was matched through a pattern.
  • $<: This is the name of the current dependency. In the case of suffix rules, this is the name of the file that is used to create the target. In our example, this would contain file.jpg
  • $*: This file is the name of the current dependency with the matched extension stripped off. Consider this an intermediate stage between the target and source files.

変換用のMakefileを作成してください。

私たちは画像の操作を行い、それからファイルサーバーにファイルをアップロードするMakefileを作成します。その後、私たちのウェブサイトがそれらを表示できるようにします。

もし一緒に進めたい場合、始める前にImageMagickパッケージがインストールされていることを確認してください。これは画像を操作するためのコマンドラインツールであり、スクリプトで使用します。

UbuntuやDebianでは、パッケージのソースをアップデートし、aptを使ってインストールしてください。

  1. sudo apt-get update
  2. sudo apt-get install imagemagick

 

Red HatまたはRockyでは、このような追加パッケージを取得するためにepel-releaseリポジトリを追加し、その後dnfを使用してパッケージをインストールする必要があります。

  1. dnf install epel-release
  2. dnf install ImageMagick

 

現在のディレクトリにMakefileという名前のファイルを作成してください。

  1. nano Makefile

 

このファイルの中で、私たちは変換目標の実施を開始します。

すべてのJPGファイルをPNGに変換してください。

私たちのサーバは、.png形式の画像のみを扱うように設定されています。このため、アップロードする前に.jpgファイルを.pngに変換する必要があります。

以上のように学んだとおり、サフィックスルールはこれを行う良い方法です。まずは、変換するフォーマットをリストアップする .SUFFIX 宣言から始めましょう。.SUFFIXES: .jpg .png。

あとで、.jpgファイルを.pngファイルに変換するためのルールを作ることができます。これは、ImageMagickスイートのconvertコマンドを使用して行うことができます。convertコマンドの構文は、from_fileからto_fileへ変換するという形です。

このコマンドを実行するためには、始めにどの形式から始まりどの形式で終わるかを指定する接尾辞規則が必要です。

メイクファイル
.SUFFIXES: .jpg .png

.jpg.png:           ## This is the suffix rule declaration

マッチするルールがあるので、実際の変換ステップを実装する必要があります。

ここで一致するファイル名が正確にわからないため、学んだ変数を使用する必要があります。具体的には、元のファイルを$<として参照し、変換先のファイルを$@として参照する必要があります。この情報をconvertコマンドに組み合わせると、次のルールが得られます。

メイクファイル
.SUFFIXES: .jpg .png

.jpg.png:
    convert $< $@

「エコーステートメントの実行時に何が起こっているかを明示的に伝えるための機能を追加しましょう。新しいコマンドの前に@記号と既存のコマンドを含めることで、実際のコマンドが出力されずに実行されるようにします。」

メイクファイル
.SUFFIXES: .jpg .png

.jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

この時点で、テストするためにファイルを保存して閉じる必要があります。

現在のディレクトリにJPGファイルを取得してください。手元にファイルがない場合は、wgetを使ってSilicon Cloudのウェブサイトから1つダウンロードすることができます。

  1. wget https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.png
  2. mv DO_Powered_by_Badge_blue.png badge.jpg

 

あなたは、メイクファイルが今のところ正常に動作しているかどうかをテストすることができます。そのために、バッジ.pngファイルの作成を要求してみてください。

  1. make badge.png

 

Output

converting badge.jpg to badge.png using ImageMagick… conversion to badge.png successful!

MakeはMakefileにアクセスし、.SUFFIXES宣言で.pngを探し、それに合致する接尾辞ルールに移ります。その後、リストされたコマンドを実行します。

ファイルリストを作成します。

この時点では、ファイルが.png形式で作成されるように明示的に指定した場合にのみ、作ることができます。

現在のディレクトリ内の.jpgファイルのリストを作成し、それらを変換する方が良いでしょう。変換するファイルを保持するための変数を作成することで、これを行うことができます。

これを行う最良の方法は、JPG_FILES=$(wildcard *.jpg)のようなワイルドカード指示子を使うことです。

JPG_FILES=*.jpgのように、bashのワイルドカードを使って対象を指定することもできますが、これには欠点があります。もし.jpgファイルが存在しない場合、実際には*.jpgという名前のファイルに対して変換コマンドを実行しようとしてしまい、失敗します。

上記で説明したワイルドカード構文は、現在のディレクトリ内の.jpgファイルのリストをコンパイルします。もし存在しない場合、変数に何も設定されません。

これを行っている間に、よくある .jpg ファイルのわずかなバリエーションを処理しようとしてみましょう。これらの画像ファイルはしばしば .jpeg 拡張子で表示されます。これらを自動的に処理するために、プログラム内で拡張子を .jpg ファイルに変更することができます。

上記の行ではなく、以下の2つを使用します。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)     ## Has .jpeg and .jpg files
JPG=$(JPEG:.jpeg=.jpg)            ## Only has .jpg files

最初の行は、現在のディレクトリ内の.jpgおよび.jpegファイルのリストを作成し、それらをJPEGという変数に格納します。

第2行はこの変数を参照し、JPEG変数内の拡張子が.jpegで終わる名前を拡張子が.jpgで終わる名前に変換するための名前変換を行います。これは$(VARNAME:.convert_from=.convert_to)の構文を使用して行われます。

これらの二行の終わりには、.jpgのファイル名だけを含む新しい変数JPGが生成されます。これらのファイルの中には実際には存在しないものもあるかもしれませんが、それは実際にはリネームが行われなかったためです。これは、新しい.pngファイルを作成するためのリストを作成するためにのみこのリストを使用するため、問題ありません。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)

今、変数PNGにリクエストしたいファイルのリストがあります。このリストには、別の名前変換を行ったため、.pngのファイル名のみが含まれています。このディレクトリにある.jpgまたは.jpegファイルであったすべてのファイルが使用され、作成したい.pngファイルのリストが作成されました。

私達は、.jpegファイルを処理するようになったことを反映するため、.SUFFIXES宣言とサフィックスルールも更新する必要があります。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

ご覧の通り、私たちはサフィックスリストに.jpegを追加し、さらにルールに別のサフィックスマッチを含めました。

目標を作成してください。

現在、私たちのMakefileにはかなりの量がありますが、まだ通常のターゲットはありません。それを修正して、PNGリストをサフィックスルールに渡すことができるようにしましょう。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

convert: $(PNG)

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

新しいターゲットは、必要とされる .png ファイルの名前を一覧にするだけです。その後、make は .png ファイルを取得できる方法があるかどうかをチェックし、接尾辞ルールを利用してファイルを取得します。

今、私たちはこのコマンドを使って、すべての.jpgおよび.jpegファイルを.pngファイルに変換することができます。

  1. make convert

 

別のターゲットを追加しましょう。画像をサーバーにアップロードする際に一般的に行われる別のタスクは、画像をリサイズすることです。正しいサイズの画像を持っていることで、ユーザーはリクエスト時に画像を即座にリサイズする必要がなくなります。

私たちが必要とする方法で、ImageMagickのmogrifyというコマンドを使用して画像のサイズを変更することができます。ウェブサイト上で画像が表示されるエリアは幅500pxですと仮定しましょう。このエリアに合わせて変換するためのコマンドは次のようになります:

  1. mogrify -resize 500\> file.png

 

このエリアに合うように、500px以上の大きさの画像をリサイズしますが、小さい画像は触れません。これが私たちが求めるものです。目標として、次のルールを追加できます。

メイクファイル
resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

これをファイルにこのように追加することができます。

メイクファイルを日本語で言い換えると、「作成ファイル」となります。
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

今、これら2つのターゲットを別のターゲットの依存関係としてつなげることができます。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

サイズを変更する際、resizeは暗黙的に同じコマンドを実行することに気づくかもしれません。しかし、常に同じ結果が得られるとは限らないため、両方を明示的に指定することにします。将来的には、convertにはより詳細な処理が含まれる可能性があります。

ウェブ化の対象は現在、画像を変換してサイズを変更します。

リモートサーバーにファイルをアップロードしてください。

ウェブ用の準備が整ったので、サーバーの静的な画像ディレクトリにアップロードするためのターゲットを作成できます。変換したファイルのリストをscpに渡すことで、これを行うことができます。

私たちのターゲットはこのようなものになるでしょう。 (Watashitachi no taagetto wa kono you na mono ni narudeshou.)

メイクファイル
upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

これによって、すべてのファイルがリモートサーバーにアップロードされます。現在、私たちのファイルはこのようになっています。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

片付ける (かたづける)

リモートサーバーにアップロードされたすべてのローカルの.pngファイルを削除するために、クリーニングオプションを追加しましょう。

メイクファイル
clean:
    rm *.png

今、私たちはリモートサーバーにファイルをアップロードした後、このターゲットを呼び出すために、上部に別のターゲットを追加できます。これは最も完全なターゲットであり、私たちがデフォルトにしたいものです。

これを特定するために、最初の利用可能な対象として設定します。これはデフォルトとして使用されます。慣習に従って、それをすべて呼びます。

メイクファイル
JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

all: upload clean

upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

clean:
    rm *.png

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

これらの最終的な手続きを行うと、Makefileと.jpgまたは.jpegファイルが入ったディレクトリに入った場合、引数なしでmakeを呼び出すことでファイルを処理し、サーバーに送信し、アップロードした.pngファイルを削除できます。

  1. make

 

ご覧の通り、タスクを連結したり、特定のポイントまで手続きを選んだりすることが可能です。たとえば、ファイルの変換のみを行い、それを別のサーバーにホストする必要がある場合、webifyターゲットを使用するだけで済みます。

結論

現時点では、一般的にMakefileを使用する方法をよく理解しているはずです。さらに具体的には、ほとんどの手順を自動化するためのツールとしてMakeを使用する方法を知っているはずです。

一部の場合には、スクリプトを書くのがより効果的かもしれませんが、Makefileはプロセス間の構造化された階層的な関係を設定する手段です。このツールの活用方法を学ぶことで、繰り返しのタスクをより管理しやすくすることができます。

コメントを残す 0

Your email address will not be published. Required fields are marked *