- 適切に設計された Makefile は、コンパイルを自動化し、依存関係を管理し、クリーンアップやパッケージ化などのタスクを容易にして、ソフトウェア プロジェクトのワークフローを最適化します。
- 変数、暗黙のルール、特別な目的、および依存関係の正しい宣言を使用すると、効率と保守性が向上し、プロジェクトを問題なく拡張できるようになります。
- CMakeのような代替手段はクロスプラットフォーム管理と高度な自動化を提供しますが、Makefileの習得は環境のプログラマーにとって不可欠です。 Unixの 建設プロセスを完全に理解します。
の世界では プログラミング ソフトウェア開発においては、ビルドとプロジェクト管理プロセスの最適化が不可欠です。特にファイル数が増え、複雑な依存関係が生じ始めると、最適化は不可欠です。そこで、次のようなツールが役立ちます。 make ファイルの準備 メークファイル 中心的な役割を担います。CまたはC++プロジェクトでバイナリ生成を自動化する方法を知りたいと思ったことがある方、あるいは単に自分のコードを扱う人の作業を楽にしたいと考えている方は、Makefileの秘密をマスターすることがほぼ必須となります。
この詳細な記事では、段階的に徹底的に、 プロジェクトの規模に関係なく、独自の Makefile を作成しカスタマイズする方法最も基本的な例からより高度な構成まで、基本的な理論と実践的なアドバイスの両方を網羅しています。 トリック よくある間違いや、CMakeのような現代的な代替手段を避けるための方法を説明します。本書の目標は、読み終える頃には、Makeの仕組み、その便利な点、そして実際の開発ニーズに合わせてMakeを最大限に活用する方法を明確かつ完全に理解していただけることです。
make とは何ですか? Makefile は何のために使用されますか?
makeはUnix/Linuxの世界で最も古く、最も多用途なユーティリティの1つです。ただし、他の実装もある OSの として Windows (NMAKEなど)。その主な目的は、特に複数のソースファイル、ライブラリ、ヘッダーで構成されるプロジェクトのコンパイルとビルドの自動化を管理することです。 Makefile は、プロジェクトの構築ロジックがキャプチャされるスクリプトです。: 何を、どのような順序で、どのような依存関係で実行するかを定義し、変更があったときに make 自体が必要なタスクのみを実行できるようにします。
典型的な問題を想像してください: 数十個の.cファイルと.hファイルを含むプロジェクトがあります。これを「手作業」でコンパイルすると、処理が遅く、面倒で、パラメータや依存関係を忘れたり、毎回すべてを再コンパイルしたりするなど、ミスが発生しやすくなります。 make と適切に設計された Makefile を使用すると、コンパイルは公園を散歩するのとほとんど同じくらい簡単になります。本当に必要なコンポーネントだけが再生成されるため、時間と手間が省けます。さらに、Makefileはコンパイルに役立つだけでなく、 一時ファイルコードのパッケージ化、バイナリのインストール、テストの実行など、さまざまなことができます。
プロジェクトでMakefileを使用する利点
- ビルド自動化: 長い手作業のラインは忘れてください コマンド コンパイルする。単純な「make」コマンドで、システムは何をすべきかを認識します。
- 依存関係の管理: 変更されたもの、または変更されたファイルに依存するものだけをコンパイルして、時間を節約します。
- 他の開発者にとっての移植性と使いやすさが向上: 誰でも標準化された方法でプロジェクトをコンパイルできます。
- 追加タスクを定義する可能性: 一時ファイルの消去、パッケージの生成、自動テストの実行など。
make は内部的にどのように機能しますか?
Make は依存関係とターゲット ロジックに基づいています。 各 "標的" Makefileにはいくつかの 「依存関係」 (ビルドに必要なファイル)とレシピ(実行するコマンド、常にタブでインデント)です。 「ターゲットを作る」プログラムは、ターゲットが既に存在するかどうか、またその依存関係が最新かどうかを分析します。依存関係がターゲットよりも新しい場合は、関連するレシピを実行してターゲットを再構築します。
基本的な構文は次のようになります。
ターゲット: 依存関係 コマンド1 コマンド2
例えば、実行ファイルをコンパイルするには メイン アーカイブから main.c y 便利です。:
メイン: main.c util.o gcc -o main main.c util.o
ご覧の通り、目標は メイン、それは main.c y 便利です。これらのファイルのいずれかが変更されると、make はすぐ下のレシピ (gcc コマンド) を実行します。
はじめに: Makefile を使わない基本的なコンパイル
Makefileの作成に入る前に、単純なプログラムが従来どのようにコンパイルされるかを見てみましょう。C言語で次のようなファイルがあるとします。
#含むint main() { printf("Hello world\n"); 戻り値 0; }
手動でコンパイルするには:
gcc hello.c -o hello
完璧です。これで実行ファイルができました。 こんにちはしかし、ソースファイルを変更すると、毎回このコマンド全体を再実行する必要があります。また、プロジェクトが大きくなるにつれて、行はどんどん長くなり、覚えるのが難しくなります。
makeを使ったコンパイル:暗黙のルールと自動ルールの魔法
make ツールは非常にスマートなので、明示的な Makefile がなくても、内部規則から単純なプログラムをコンパイルできます。。 たとえば、次のような場合 こんにちは。 フォルダ内で以下を実行します:
こんにちは
Make はファイルの存在を検出し、その暗黙のルールを使用して適切なコンパイラを使用してバイナリを生成します。 こんにちは ソースから こんにちは。実行ファイルが既に作成されており、ソースファイルよりも新しい場合は何も起こりません。実行ファイルを削除して再度実行すると、 こんにちはパラメータを覚えていなくても再コンパイルされます。
実際のプロジェクトで Makefile が不可欠なのはなぜですか?
プロジェクトに複数のソースファイル、ヘッダー、相互依存関係が含まれるようになると、makeの内部ルールの魔法はすぐに効力を失います。そうなると、makeだけでは何をインクルードすべきか、どのコンパイラオプションを使用すべきか、オブジェクトをどのようにリンクすべきかが分からなくなってしまいます。 そこでMakefileがあなたの最強の味方となるのですこれは、各ファイルをどのように処理するか、個々のオブジェクトをどのようにコンパイルするか、それらをどのようにリンクして 1 つ以上の実行可能ファイルを作成するかを make に指示します。
Makefileの構造:理論と実践
従来の Makefile は、ターゲット、その依存関係、およびコマンド レシピの 3 つの部分で構成される 1 つ以上のルールで構成されます。 実行されるコマンドラインは、スペースではなくタブで始まる必要があります(はいまたははい)。そうしないと、make は失敗します。
ターゲット: 依存関係 コマンド1 コマンド2 ...
用語の説明:
- Objetivo: これは通常、実行可能ファイル、オブジェクト ファイル、または「clean」などの内部アクションの名前です。
- 依存関係: ターゲットをビルドするためには、存在し、最新でなければならないファイル。依存関係のいずれかがターゲットよりも新しい場合、makeはレシピを実行します。
- コマンド: コンパイル、削除、パッケージ化などの命令は次のようにインデントする必要があります。 集計者.
非常に基本的なMakefileの例 2 つのソース ファイルをコンパイルして実行可能ファイルを生成するには、次の手順を実行します。
テスト: test.o main.o gcc -o test test.o main.o test.o: test.c gcc -o test.o -c test.c main.o: main.c test.h gcc -o main.o -c main.c
だから、もしあなたが make最初にオブジェクトが存在するかどうかを確認し、存在しない場合はコンパイルしてから、テスト実行ファイルにリンクします。
実際のワークフローに基づいて重要な概念を確認する
プロジェクトが複雑になるにつれて、依存関係、オブジェクト、ルールを整理して、必要なものだけを再コンパイルする方法を理解することが重要になります。 たとえば、.h ファイルを 1 つだけ変更する場合は、そのファイルを含むすべての .c ファイルが再コンパイルされていることを確認する必要がありますが、それ以外は何もする必要はありません。.
次のようなファイルセットがあるとします。
- メイン.c: メインプログラム、hello.h を含む
- こんにちは。: 補助機能
- こんにちは。: 関数ヘッダー
シンプルだが機能的な Makefile は次のようになります。
hello: main.o hello.o gcc -o hello main.o hello.o main.o: main.c hello.h gcc -c main.c hello.o: hello.c hello.h gcc -c hello.c
これで、すべてはそれが何に影響するかに依存し、変更されたもの、または変更されたものに依存するものだけが再コンパイルされます。
Makefileの変数:再利用と簡素化
Makefileの最も強力な武器の一つは変数であるこれらを使用すると、たとえば、コンパイラ、共通フラグ、バイナリ名、オブジェクトなどを定義できます。この方法では、1 行を変更するだけで、Makefile の残りの部分が自動的に更新されます。
典型的な例:
CC=gcc CFLAGS=-I. OBJ=main.o hello.o hello: $(OBJ) $(CC) -o hello $(OBJ) $(CFLAGS) %.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS)
ここに:
- $(CC) コンパイラ変数です。
- $(CFLAGS) コンパイラに渡されるオプションです。
- $(OBJ) オブジェクトのリストが含まれます。
- $@ ターゲットの名前に置き換えられ、 $< 最初の依存関係によって。
これにより、より汎用的で柔軟なルールを生成できます。例えば、別の.oファイルを追加する場合、OBJ変数を変更するだけで済みます。
推奨事項: 繰り返しを避け、変更を容易にするために変数を使用します。
Makefile の先頭で変数を定義することは、プロジェクトの保守性と可読性を高めるための重要な方法です。実行可能ファイル名からヘッダー、オブジェクト、ソース ディレクトリまですべてを定義できます。
CC=gcc CFLAGS=-Wall -g -I./include OBJ=main.o func.o utils.o my_program: $(OBJ) $(CC) -o $@ $^ $(CFLAGS)
$^ すべての依存関係(この場合はオブジェクト)を表します。したがって、ソースリストを変更するには、OBJ変数を変更するだけで済みます。
特別なルールの追加:すべて、クリーン、その他の便利なタスク
ほとんどのプロジェクトでは、コンパイル以外にも追加のタスクが必要です。 例えば、
- 中間ファイル(.o)とバイナリファイルのクリーニング( ).
- すべてのバイナリを一度にビルドする(を).
- パッケージ、ドキュメント、テストなどの生成。
all と clean を含む Makefile の例:
CC=gcc CFLAGS=-I. OBJ=main.o hello.o all: hello hello: $(OBJ) $(CC) -o hello $(OBJ) $(CFLAGS) %.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS) clean: rm -f hello *.o
さて、走ってみると make o すべてを作るバイナリがコンパイルされます。 きれいにするバイナリおよびオブジェクト ファイルは削除されます。
重要: ディレクトリ内に「clean」というファイルがある場合、ターゲットを次のように宣言しない限り、makeは「make clean」を使用してもレシピを実行しません。 .PHONY:
.PHONY: クリーン クリーン: rm -f hello *.o
依存関係の最適化とメンテナンス: .h ファイルを正しくインクルードする
初心者が犯しがちな間違いの 1 つは、.h ファイルへの依存関係を宣言しないことです。これにより、ヘッダーが変更されても、ヘッダーを含むソースが再コンパイルされなくなる可能性があります。
これを修正するには、各オブジェクトの対応する .h オブジェクトへの依存関係を明示的に宣言します。または、自動ルールを使用します。
%.o: %.c hello.h $(CC) -c -o $@ $< $(CFLAGS)
だから、毎回 こんにちは。 変更すると、make は依存オブジェクトを再コンパイルします。
高度な依存関係の自動化: MakeDepend とその代替手段
大規模なプロジェクトの場合、依存関係を手動で管理するのは非常に面倒です。特に、いくつかの.hファイルが他の.hファイルをインクルードしている場合、このような問題が発生します。 依存するは、これらを自動的に生成し、Makefileを最新の状態に保つように設計されています。特にクロスプラットフォームで作業する場合など、より複雑なケースに対処するには、効率的で柔軟な方法を理解することが役立ちます。
- 実行:
makedepend -I./include *.c
- これにより、Makefile の末尾に正しい依存関係行が追加されます。
プロフェッショナルなMakefileの典型的な構造
CFLAGS=-I./include OBJECTS=main.o utils.o func.o SOURCES=main.c utils.c func.c プログラム: $(OBJECTS) gcc $(OBJECTS) -o プログラム 依存: makedepend $(CFLAGS) $(SOURCES) クリーン: rm -f プログラム *.o
複数のディレクトリにまたがるプロジェクトの Makefile
ディレクトリの数が増えると (たとえば、モジュールやライブラリが複数ある場合)、他のセカンダリ Makefile に委任するメイン Makefile が存在するのが普通です。たとえば、ルート ディレクトリに、次のように各モジュールの Makefile を呼び出す Makefile を配置できます。
モジュール1: make -C モジュール1 モジュール2: make -C モジュール2
そして、各フォルダーに生成されたすべての .oo ライブラリの最終リンクです。
Makefile を非 Unix 環境に統合する: Visual Studio と NMAKE
Windows で作業している場合、Visual Studio は Makefile ベースのプロジェクトのサポートを提供します。 NMAKE ツールまたは Makefile プロジェクト オプションを使用すると、Makefile テンプレートからプロジェクトを作成し、ウィザードからコンパイル、クリーンアップ、および出力コマンドを指定し、IntelliSense や高度なデバッグなどのツールを活用して Makefile を IDE に統合できます。
Visual Studio 2017 以降の場合:
- 「ファイル > 新規 > プロジェクト」を選択し、「Makefile」を検索してテンプレートを選択し、コンパイル、クリーンアップ、デバッグのコマンドを定義します。
- プロジェクト プロパティ タブでは、パス、コマンド、オプションを設定できます。
- より良い統合については、次のチュートリアルをご覧ください。 Windowsでエラーを管理する方法.
最新の Makefile の代替: CMake など
Makefile は Unix 環境では事実上の標準のままですが、CMake などの他のシステムもかなり人気が高まっています。クロスプラットフォーム対応と、大規模プロジェクトでも扱いやすい構文が特に優れています。実用的なガイダンスとして、CMakeと併せて知っておくと役立つかもしれません。
CMake は高レベルの記述から Makefile を生成します。シンプルなプロジェクトであれば、1 つのファイルで十分です。 CMakeLists.txt そう:
cmake_minimum_required(バージョン2.8) プロジェクト(Hello) add_executable(hello main.c hello.c)
次に「cmake .」を実行してMakefileを生成し、「make」でコンパイルします。サブディレクトリを作成することもできます。 ビルド、 実行する シマケ .. そこからそして make プロジェクトを整理し、拡張性を保つため。
Makefile のベストプラクティスと重要なヒント
- タブに注意してください: レシピ コマンドは、スペースではなく、常にタブで始まる必要があります。
- .h ファイルを含むすべての依存関係を正しく宣言します。 これにより、不適切な再コンパイルや古いコードによるエラーを防ぐことができます。
- 実行可能ファイルの名前、ディレクトリ、フラグには変数を使用します。 変更を容易にし、エラーを削減します。
- clean、all、depend などの目標が含まれます。 こうすることで、プロジェクトの使用と保守が容易になります。
- 大規模なプロジェクトの場合は、makedepend または CMake の使用を検討してください。 これにより、依存関係とスケール管理をより適切に自動化できます。
- 一時ファイルの消去をサポートします: コードの共有時に問題を引き起こす可能性のある未使用のオブジェクトやバイナリをディレクトリに詰め込まないでください。
- 最初のターゲットをメインの実行可能ファイルまたは「all」ターゲットにします。 したがって、誰かが引数なしで make と入力すると、これがコンパイルされます。
- 適切な場所に Makefile にコメントを追加します。 これらは必須ではありませんが、他の人(または数週間後のあなた自身)が理解しやすくなります。
よくある間違いとその回避方法
- コマンドレシピを開始するときにスペースとタブが混乱します。
- ヘッダー ファイル (.h) への依存関係を宣言し忘れる。
- ファイルを生成しない場合は、ターゲットを .PHONY として宣言しないでください。その名前のファイルまたはディレクトリが存在する場合、異常な動作が発生する可能性があります。
- 名前やパスに変数を使用しないでください。変数を使用すると情報が重複し、メンテナンスが困難になります。
- 新しいビルドの前に一時ファイルまたはバイナリ ファイルをクリーンアップしないと、混乱を招くエラーが発生する可能性があります。
複雑なタスク向けにMakefileをカスタマイズする
コンパイルとクリーニングに加えて、次のカスタム ルールを作成できます。
- コードを ZIP または TAR ファイルにパッケージ化します。
- コンパイル後に実行可能ファイルを自動的に起動します。
- ドキュメントを生成します (たとえば、Doxygen を使用)。
- 自動テストを実行し、結果を表示します。
- 関連するソースとファイルのみをコピーして、配布用のコードを準備します。
たとえば、圧縮ファイルを生成するには、次のようにします。
dist: zip my_project.zip *.c *.h Makefile README.md
完全なプロジェクトのための包括的な Makefile の例
CC=gcc CFLAGS=-Wall -g -I. DEPS=hello.h OBJ=main.o hello.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hello: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) .PHONY: clean dist clean: rm -f hello *.o
実際の事例と最終的な推奨事項
長年にわたり、make と Makefile は、C、C++、その他多くの言語でのプロジェクトを効率的かつ組織的にコンパイルするために不可欠なものとなっています。CMake のような現代的な代替手段もありますが、Makefile を習得することは、あらゆる本格的な開発環境で役立つ基礎となります。
各プロジェクトごとにMakefileの特定の調整が必要になる場合があることに注意してください。最も重要なのは、ターゲット、依存関係、レシピのロジックを理解することです。そうすれば、必要なタスクを自動化することでmakeのパワーを最大限に活用できるようになります。他のプロジェクトのMakefileを研究したり、自動化された依存関係管理ツールを使ったり、追加のターゲットを試したりしてみてください。
ここで学んだことをすべて適用すると、プロジェクトの規模に関係なく、人的エラーを減らし、時間を節約し、他の人 (または将来自分自身) がソフトウェアを専門的かつ効率的に構築、保守、配布しやすくなります。
バイトの世界とテクノロジー全般についての情熱的なライター。私は執筆を通じて自分の知識を共有するのが大好きです。このブログでは、ガジェット、ソフトウェア、ハードウェア、技術トレンドなどについて最も興味深いことをすべて紹介します。私の目標は、シンプルで楽しい方法でデジタル世界をナビゲートできるよう支援することです。