UbuntuでVSCodeでC++でHello World!:ビルド環境を実用向けに改良する

 前回VSCodeでC++をコンパイル/ビルド、そしてデバッグ実行する環境を作ってみました:

 本質的にはmakefileをmakeするようVSCodeからコマンドを叩いているだけなのですが、Ctrl+Shift+Bでビルド、F5でデバッグ実行できるので、Visual Studioに近い感覚で開発できます。

 ただ、ビルド環境としてはまだまだ不満点があります。今回はそれを改善して、Visual Studioのような開発しやすいビルド/デバッグ環境を目指します。具体的には、

  • srcフォルダ下に置いたcppでコンパイルできる

  • includeフォルダ下に置いたヘッダーのパスを解決できる

  • デバッグ版はdebugフォルダ、リリース版はreleaseフォルダ下に成果物(オブジェクトファイル、実行ファイル)ができる

  • VS CodeのCtrl+Shift+Bでデバッグ/リリース版双方のビルドを選択、F5でデバッグ実行が引き続きできる

これらが実現出来ます!

 まずは、前回までの環境で何が良くないのか目につく不満点を挙げて状況を整理する所から始めましょう。(以降は前回の記事に超依存していますので、まだの方は上のリンクから飛んで一読下さい)

不満点1:cppをサブフォルダ下にまとめたい

 通常cppファイルはプロジェクトフォルダ直下には置かず、srcとかcodeというサブフォルダの下に配置します。それらフォルダの下にpackagesやprjなどのサブフォルダを自由に掘る事も普通です。ビルド時にはそれらフォルダ内にあるcppを全部拾って来る必要がありますが、前回までのmakefileではそれができません。その機構をmakefileに記述する必要があります:

不満点2:ヘッダーファイルは別フォルダにまとめたい

 ヘッダーファイルはプロジェクトフォルダ下のincludeフォルダ下にまとめる事が多いです:

cppとヘッダーとでフォルダを分けるのはそれなりの理由があるので、ここはその流儀に従うとして、ヘッダーファイルがcppファイルと別のフォルダにあるとcpp側でのヘッダー参照で問題が起こります。

 例えば上右図のmain.cppで、

#include "helloworld.h"

int main() {
   ...
}

このようにヘッダーをインクルードした時、特に何も考慮しなければ、main.cppと同じフォルダ内にhelloworld.hが無いとコンパイルエラーになります。

 右のフォルダ構造で無理やりコンパイルエラーを回避するなら、

#include "../include/helloworld.h"

int main() {
   ...
}

こんな感じでmain.cppからの相対パスで参照すればよいのですが、各cppでこんなフォルダ構造依存なコードを書いてしまうとあっという間に破綻してしまいますし、コードを別のプロジェクトで使う際の再利用性も著しく下がります。

 これを解決するには、コンパイル時にヘッダーファイルの参照フォルダ(上の場合は./include)をコンパイラに教えてあげます。するとその参照フォルダを起点としてヘッダーファイルを検索してくれるので、上コードでもコンパイルがちゃんと通るようになります。gccのコマンドで書くと、

g++ -g main.cpp -o main.o -Iinclude

-Iオプションでパスを指定します。よってこのオプションを追加する仕組みをmakefileに組み込む必要があります。

不満点3:オブジェクトファイルは一時フォルダ下に出力したい

 コンパイル時に生成される中間ファイルであるオブジェクトファイル(.o)は、前回のmakefileだとプロジェクトフォルダ下に出力されますが、これははっきりとよろしくありません:

普通はobjフォルダなどの下にまとめて出力します。Visual Studioもそうなっていますよね。オブジェクトファイルは一時的な物で普通gitやsubversionでは無視ファイルの対象です。ですから尚更objフォルダ下にまとめないと面倒極まりない訳です。

不満点4:リリース版のビルドもしたい

 前回のmakefileは「-g」フラグが常についていました。これはデバッグ版の実行ファイルを常に作る事になります。デバッグ版は低速ですしログ出力など余計なアウトプットもあるため製品用にはもちろん使えません。なのでリリース版をビルドできなければなりませんが、現状その機構がありません。リリース版のmakefileを通してVS Codeでデバッグ時にはデバッグ版を、製品用にはリリース版をビルドできるよう振り分ける必要があります:

 と言う事で、問題点からやりたい事をまとめると

  • srcフォルダ下にあるcppをmakefileでかき集めてビルドする

  • includeフォルダにまとめたヘッダーファイルをコンパイル時に参照する

  • objフォルダにオブジェクトファイルを出力する

  • デバッグ版/リリース版を別ビルドする

  • これらをVSCodeでのみ操作できるようにする

これらの改善と実現を目指しましょう。

 尚、これ以降は前回の環境をガシガシ書き換えます。文末に最終的なmakefileとVSCodeのlaunch.json、tasks.jsonを挙げてはありますが、前回までの環境を用意した上で以降の変更プロセスを実際に手を動かして追う事を強く推奨致します。こういうのって一度は手を動かして覚えないと身に付かないもんですし、トラブルが起きた時に内容を理解していないとパニくって真っ白な灰になって終わりますからね(^-^)b

makefileでsrcフォルダ下のcppをかき集める

 前回のmakefileで以下のような記述がありました:

# コンパイラとフラグ
CXX = g++
CXXFLAGS = -g

# ソースファイルの自動検出
SRC = $(wildcard *.cpp)

 makefile内には変数を定義できます。上のCXX、CXXFLAGS、SRCはすべて変数です。注目はSRC。この右辺に$(wildcard *.cpp)とあります。名前から察しがつくと思いますが、ワイルドカードでcppという拡張子を持ったファイル名を全部かき集めています。変数にはそういうリストも格納できます。ただしwidlcardはmakefileがあるフォルダ内のみ有効で、サブフォルダを再帰的には検索してくれません。

 makefileでは$(○○ ***)のような関数的な働きをしてくれるコマンドが色々用意されています。この辺りは出会った時に慣れろです(^-^)b

 今は上図にあるようにsrcフォルダ下のcppをかき集めて欲しいので、このSRC部分を以下のように書き換えます:

PROJECT_ROOT = .
SRC_ROOT = src

# ソースファイルの自動検出
SRC_DIR = $(PROJECT_ROOT)/$(SRC_ROOT)
SRC = $(shell find $(SRC_DIR) -type f -name "*.cpp")

 ポイントは$(shell)関数です。SRC_DIR変数を展開するとこういう記述になっています:

SRC = $(shell find ./src -type f -name "*.cpp")

 $(shell)関数は後述のターミナルコマンドを直接叩けます。上ではfindコマンドでsrcフォルダ内のファイルを検索しています。-typeフラグにfを指定するとファイルのみ検索対象にします。-nameは検索したいファイル名で、*.cppとあるので拡張子がcppなファイルのみ全検索しています。

 findコマンドはサブフォルダも掘ってくれるので、この結果SRC変数にはsrcフォルダ下やさらに下のサブフォルダにある全cppファイルのリストが格納されます。不満点1はこれで解決です。

ヘッダーファイルをincudeフォルダ下に置く

 先の節でcppをsrc下へ置けるようになりました。そこで前回のサンプルを次のようなフォルダとファイルの構成にしてみます:

srcフォルダを設けて、その下に2つのcppを移動しただけです。先の改良makefileの元でCtrl+Shift+Bでコンパイルしてみると…

「main.cpp内でインクルードしているhelloworld.hが無いよ」と言われています。これはその通りで、main.cppは、

helloworld.hが自分と同じフォルダ内にある(もしくはパスが解決されている)としています。helloworld.hは一つ上のフォルダにあるので、この記載だとパスがおかしいんです。また今コンパイラにはヘッダーファイルのパスについての情報は何も設定されていません。そのため「見つからない」と怒られたんですね。

 C++は昔からこのようにcppファイルの移動に弱い性質があります。

 これを解決するにはインクルードを"../helloworld.h"と書き直せば良いわけですが、これは修正法としてあまり良くありません。だってこんなcppファイルが何十個もあったら、都度それを書き換える羽目になるわけでして…(-_-;。それよりもhelloworld.hがあるフォルダをIオプションでコンパイラに教えた方が賢明です。

 現在makefile内でコンパイル、つまりオブジェクトファイルを生成している部分はこうなっています:

CXX = g++
CXXFLAGS = -g

....

# オブジェクトファイルの生成ルール
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

 生成ルールの所はmake時に例えばこういうコマンド実行に置き換わります:

g++ -g -c main.cpp -o main.o

 これはmain.cppをコンパイルしてmain.oを作りなさいという命令です。この際gccはIオプションによって参照するインクルードフォルダのパスを追加する事ができます。

 次のように書き換えてみましょう:

CXX = g++
CXXFLAGS = -g
INCLUDES = -Iinclude

# オブジェクトファイルの生成ルール
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

INCLUDESという変数を設けて、そこに「-Iinclude」とincludeフォルダのパスを代入します。-Iフラグはその後ろにスペースを入れないのに注意です。

 INCLUDE変数はオブジェクトファイルを生成する時のフラグとして追加されているのがわかりますね。

 これでコンパイラはincludeフォルダもヘッダーの検索に使ってくれるようになったので、次にプロジェクトフォルダ下にincludeフォルダを新設し、そこにhelloworld.hを移動します:

インクルードパスを考慮したmakefileでビルドすると、今度はちゃんとビルドが通ります!

 ところで、-Iフラグはそのパスのみ有効で、サブフォルダまでは見てくれません。includeフォルダ内をさらに深掘る場合は都度インクルードパスを-Iフラグで追加する必要があります:

CXX = g++
CXXFLAGS = -g
INCLUDES = -Iinclude -Iinclude/prj -Iinclude/packages

 この手書き部分…まぁ大した頻度ではないので手書きでも良いのですが、そうするとmakefileがそのプロジェクトに依存してしまい使い回しが効かなくなります。なので、蛇足かもしれませんがここも自動的にインクルードパスをかき集めるよう改良してみます。

 方法は不満点1に出てきたfindコマンドです:

INCLUDE_ROOT = include
 
# インクルードフォルダパス
INCLUDE_DIRS = $(shell find $(PROJECT_ROOT)/$(INCLUDE_ROOT) -type d)
INCLUDES = $(addprefix -I, $(INCLUDE_DIRS))

 findコマンドで-typeにdを指定する事で、今度はディレクトリパスを拾って来てくれます。ただこれをインクルードフラグにするには頭に「-I」を付けなければならないので、$(addprefix)関数というのでそのプレフィック作業をしています。

 これでインクルードパスの問題も解決です。不満点2も改善できました!

オブジェクトファイルをobjフォルダ下に出力する

 ここまでの段階でビルドすると、オブジェクトファイルが次のようにsrcフォルダ下に生成されてしまいます:

オブジェクトファイルは一時ファイルなのに、ソースファイルと一緒になるのは不満点というよりもはや問題です。これは別のobjフォルダ下に出力しないと不便しか起こしません。

 前回のmakefileではオブジェクトファイルのリストを次のように作っています:

# オブジェクトファイルのリストを生成
OBJ = $(SRC:.cpp=.o)

これはSRCリストにあるcppファイル(./src/main.cpp、./src/helloworld.cpp)の拡張子を.oに置き換える関数になっています。よって./src/main.oというcppと同じフォルダへ出力するパスが出来上がってしまうんです。ここが根本的に良くありません。

 そこでこの部分を次のように書き換えます:

PROJECT_ROOT = .
OBJ_ROOT = obj

# オブジェクトファイルのリストを生成
OBJ_DIR = $(PROJECT_ROOT)/$(OBJ_ROOT)
OBJ = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRC))

 $(patsubst)関数は置換関数です。引数が3つあります。
 第1引数は置換元となる文字列で、上では$(SRC_DIR)/%.cppとなっています。SRC_DIRはcppが置かれているsrcフォルダへのパス(./src)です。続く謎の「%」ですが、これはいわばワイルドカード文字列(パターンマッチ)です。例えば./src/prj/main.cppというパスだったとすると、%は「prj/main」を表すんです。
 第2引数は置換後の文字列で、ここの%に先のパターン文字列がスポンと入り込みます。上の場合だとOBJ_DIRが./objなので「./obj/prj/main.o」というパスに置き換わるという事になります。
 第3引数は実際に処理を行う文字列のリストで、SRCなので今は「./src/main.cpp」「./src/helloworld.cpp」という2つのソースファイルのパスが格納されています。
 ここからOBJには「./obj/main.o」「./obj/helloworld.o」がリストアップされるんですね。欲しいオブジェクトファイルのパスができました。

 このOBJリストを用いれば、実行ファイルを生成する以下のルール、

# 実行ファイルの生成ルール
$(TARGET): $(OBJ)
	$(CXX) $(CXXFLAGS) -o $@ $^

ここはこのまま流用できます。ルールの右辺には先のオブジェクトファイルパスのリストが入り、左辺には生成物であるhello_worldが来ます。それらをルールの種として、下のgccのリンカーコマンドを実行しています。ぱっと見コマンド部分が抽象的過ぎて謎に見えますがww、ちゃんと動きます(^-^)

 一方オブジェクトファイルを生成しているルールは今はこうなっています:

# オブジェクトファイルの生成ルール
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

上のルールだと左辺と右辺の%に同じ文字列(パス)が来てしまいますが、今はもうcppとオブジェクトファイルは別パスに位置する前提になったため、このままだとうまくいきません。そこでここを次のように書き換えます:

# オブジェクトファイルの生成ルール
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

 ルール左右のファイルの親フォルダをそれぞれ明記するよう変更しました。こうする事でmakeはこのパターンにマッチするルール右側のcppファイルを適切に見つけてきてくれて、左辺へ%部分をはめ込んでくれるんです。便利〜

 ルールに適合するcppが見つかった場合、下のコマンドが実行されます。その1行目ではmkdir、つまり出力フォルダを生成しています。出力先のフォルダがないと出力エラーになってしまうためこれが必要になります。
 -pフラグは指定のフォルダが深い場合もその中間フォルダ含めて一気に作る指示です。ほんと色々便利にできてます(^-^)。$(dir)関数は引数のファイルパスのディレクトリ部だけを抽出してくれます。例えば./src/prj/hoge/main.cppだとしたら./src/prj/hogeが取り出されます。

 オブジェクトファイル生成部(コンパイルの本丸)は手付かずでOK。

 さ!これでビルドを回すと…

objフォルダが出来て、その下にmain.oとhelloworld.oが出力されています!そしてhello_world実行ファイルもちゃんとできていますよね!これで不満点3も解消です!!

 ここまでの段階での改良makefileは以下の通りです:

# コンパイラとフラグ
CXX = g++
CXXFLAGS = -g
PROJECT_ROOT = .
INCLUDE_ROOT = include
SRC_ROOT = src
OBJ_ROOT = obj

# 実行ファイル名
TARGET = hello_world_d

# インクルードフォルダパス
INCLUDE_DIRS = $(shell find $(PROJECT_ROOT)/$(INCLUDE_ROOT) -type d)
INCLUDES = $(addprefix -I, $(INCLUDE_DIRS))

# ソースファイルの自動検出
SRC_DIR = $(PROJECT_ROOT)/$(SRC_ROOT)
SRC = $(shell find $(SRC_DIR) -type f -name "*.cpp")

# オブジェクトファイルのリストを生成
OBJ_DIR = $(PROJECT_ROOT)/$(OBJ_ROOT)
OBJ = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRC))

# デフォルトのターゲット
all: $(TARGET)

# 実行ファイルの生成ルール
$(TARGET): $(OBJ)
	$(CXX) $(CXXFLAGS) -o $@ $^

# オブジェクトファイルの生成ルール
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

# クリーンアップ
clean:
	rm -f $(OBJ) $(TARGET)

前回のmakefileよりも内容は複雑にはなりましたが、これでプロジェクトのファイル構成がすっきり整理され、はるかに使いやすい環境になりました。

リリース版のビルドプロセスを追加する

リリース版makefileを作る

 最後の不満点4も解消しましょう。ここまでのmakefileは

CXXFLAGS = -g

とあるように、-gフラグ、つまりデバックビルド前提です。よってこのmakefileだとリリース版を出力できません。

 ではどうするか?これは2つアプローチがあるかなと思います。一つは先のmakefile内にリリース版の流れをゴリっと組み込む方法。利点は共通項を文字通り共通に使える点ですが、デメリットは可読性とスケーラビリティが著しく下がる事です。

 もう一つは既存のデバッグ版makefileをmakefile_dとして、それをごっそり別ファイルにコピーして、リリース用のmakefileを別に設ける事。利点はスケールしやすい事。デメリットは双方のmakefileを管理する手間が増える事でしょうか。

 どちらも一長一短があるのですが、今回は後者を採用します。なぜならとにかくスッキリするから!

 先のmakefileはデバッグ版なので、まずはmakefile_dとリネームします。次にスクリプト内の頭にある変数定義を改良してdebugフォルダ下にオブジェクトファイルと実行ファイルが生成されるようちょっとだけ変更します:

# コンパイラとフラグ
CXX = g++
CXXFLAGS = -g
PROJECT_ROOT = .
INCLUDE_ROOT = include
SRC_ROOT = src
TARGET_ROOT = debug
TARGET_NAME = hello_world_d
OBJ_ROOT = $(TARGET_ROOT)/obj
BIN_DIR = $(PROJECT_ROOT)/$(TARGET_ROOT)/bin

# 実行ファイル名
TARGET = $(BIN_DIR)/$(TARGET_NAME)

# 実行ファイルの生成ルール
$(TARGET): $(OBJ)
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -o $@ $^

 TARGET_ROOTに生成物をまとめるフォルダ名を指定します。またTARGET_NAMEに実行ファイル名を記載します。ここではデバッグ版なのでhello_world_dとサフィックスを付けてリリース版と区別するようにしました。OBJECT_ROOTやBIN_DIR(実行ファイルの格納先)もそれらを利用し作っています。TARGETもこれで抽象化しています。

 もう一つ実行ファイルの生成ルールのところでbinフォルダが出来るよう$(dir)関数を追加しています。これはもうわかりますよね。

 これだけでオブジェクトファイルと実行ファイルが本章冒頭の挿絵の構成になってくれます。

 この抽象化でリリース版のmakefileは設定が楽になります:

CXXFLAGS = 
TARGET_ROOT = release
TARGET_NAME = hello_world

 変更点だけピックアップするとこの3点だけです。ビルドオプションであるCXXFLAGSが空になっています。これでリリース版になります。TARGET_ROOTはreleaseですね。そして実行ファイル名TARGET_NAMEはhello_worldとサフィックスを省きます。

 これでもうリリース版がビルドできるのですから、スッキリですよね。

VSCodeにデバッグビルド、リリースビルドを作る

 デバッグ版のmakefileをmakefile_dとリネームし、実行ファイル名をhello_world_dとしたので、VSCodeのtask.jsonをそれに対応するよう修正する必要があります:

{
    "tasks": [
        {
            "type": "shell",
            "label": "C/C++: g++ build with makefile (debug)",
            "command": "make",
            "args": ["-f", "makefile_d"],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Runs the Makefile to build the project (debug).",
            "problemMatcher": "$gcc"
        }
    ],
    "version": "2.0.0"
}

 まずlabelの最後に(debug)を付けました。これはrelease版と区別するためです。makeの引数であるargsは、以前は空でしたが今回は明確な引数が必要です。「-f」はmakeファイル名を指定する時のフラグです。デバッグ版であるmakefile_dを指定しています。detailにも(debug)を入れておきましょう。

 次にこの雛形をコピーして、すぐ下に複製し、リリース版のタスクパラメータを追記します:

{
    "tasks": [
        {
            "type": "shell",
            "label": "C/C++: g++ build with makefile (debug)",
            "command": "make",
            "args": ["-f", "makefile_d"],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Runs the Makefile to build the project (debug).",
            "problemMatcher": "$gcc"
        },
        {
            "type": "shell",
            "label": "C/C++: g++ build with makefile (release)",
            "command": "make",
            "args": [],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Runs the Makefile to build the project (release).",
            "problemMatcher": "$gcc"
        }
    ],
    "version": "2.0.0"
}

labelの最後に(release)を追加しデバッグ版と区別します。labelはlaunch.jsonで参照しているのでこれは必須です。makeの引数は空でOK。デフォルトは「makefile」というファイル名を対象にしてくれるためです。detailにも(release)を付けておきます。変更はこれだけ。シンプルですよね。

 label名を変更したので、それを参照しているデバッグ実行であるlaunch.jsonも対応させます。またリリース版の実行もサポートします:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C/C++: g++ build and debug with Makefile (debug)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/debug/bin/hello_world_d",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "preLaunchTask": "C/C++: g++ build with makefile (debug)",
            "miDebuggerPath": "/usr/bin/gdb"
        },       
        {
            "name": "C/C++: g++ build with makefile (release)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/release/bin/hello_world",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "preLaunchTask": "C/C++: g++ build with makefile (release)",
        }
    ]
}

上がデバッグ版です。変更点はpreLaunchTaskの名前です。(debug)が入ったタスクを指定します。また実行ファイルパスの指定であるprogramはdebug/bin/hello_world_dなのに注意です。

下はリリース版です。デバッグ版と違いデバッグ情報を使わないのでMIModeとmiDebuggerPathを削除しています。

 これでリリース版のビルドと実行もVS Codeでできるようになりました。Ctrl+Shift+Bでコンパイルを試みると、上にdebugとreleaseのどちらにするか選択欄が出るはずです:

debug版でビルドしたらdebug/obj下にオブジェクトファイルができ、hello_world_dがbinフォルダ下に作成されます。release版の場合はreleaseフォルダ下にobjとbinフォルダがそれぞれ出来ます。

 F5でのデバッグ実行は現在選択されているものがそのまま実行されます。これを変更したい時は画面左下にある実行欄をクリックすると、デバッグ実行の選択欄が上に出ます:

「C/C++: g++ build with〜」って書かれている文字列がそれです。これすごいわかりにくいんですよねぇ…。上に出る選択欄はこちら、

debug版を選ぶとデバッグ情報も出力されるのでステップ実行が可能です。一方リリース版はデバッグ情報を出力しないようlaunch.jsonに書き込んだので、ステップ実行はできません。ブレイクポイントを付けても素通りです。リリース版は最適化がかかっていてまともにデバッガーが働かないので、今回のようにデバッグ実行自体を抑制した方が無難です。

 という事で、ここまでで挙げた不満点を全部解消した開発がついにできるようになりました!

 今回は前回のサンプル的なmakefileやVSCodeの環境を色々改善して、Visual Studioでの開発に近い環境を作ってみました。これでかなりご機嫌にC++の開発ができると思います。これは一つの環境構築の例ですが、ここまで理解を進められれば、後は個人の好みやプロジェクトの都合でmakefileやVSCodeをカスタマイズできるはずです!

ではまた(^-^)/

完成版はこちら

デバッグ版(makefile_d)

# コンパイラとフラグ
CXX = g++
CXXFLAGS = -g
PROJECT_ROOT = .
INCLUDE_ROOT = include
SRC_ROOT = src
TARGET_ROOT = debug
TARGET_NAME = hello_world_d
OBJ_ROOT = $(TARGET_ROOT)/obj
BIN_DIR = $(PROJECT_ROOT)/$(TARGET_ROOT)/bin

# 実行ファイル名
TARGET = $(BIN_DIR)/$(TARGET_NAME)

# インクルードフォルダパス
INCLUDE_DIRS = $(shell find $(PROJECT_ROOT)/$(INCLUDE_ROOT) -type d)
INCLUDES = $(addprefix -I, $(INCLUDE_DIRS))

# ソースファイルの自動検出
SRC_DIR = $(PROJECT_ROOT)/$(SRC_ROOT)
SRC = $(shell find $(SRC_DIR) -type f -name "*.cpp")

# オブジェクトファイルのリストを生成
OBJ_DIR = $(PROJECT_ROOT)/$(OBJ_ROOT)
OBJ = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRC))

# デフォルトのターゲット
all: $(TARGET)

# 実行ファイルの生成ルール
$(TARGET): $(OBJ)
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -o $@ $^

# オブジェクトファイルの生成ルール
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

# クリーンアップ
clean:
	rm -f $(OBJ) $(TARGET)

リリース版(makefile)

# コンパイラとフラグ
CXX = g++
CXXFLAGS =
PROJECT_ROOT = .
INCLUDE_ROOT = include
SRC_ROOT = src
TARGET_ROOT = release
TARGET_NAME = hello_world
OBJ_ROOT = $(TARGET_ROOT)/obj
BIN_DIR = $(PROJECT_ROOT)/$(TARGET_ROOT)/bin

# 実行ファイル名
TARGET = $(BIN_DIR)/$(TARGET_NAME)

# インクルードフォルダパス
INCLUDE_DIRS = $(shell find $(PROJECT_ROOT)/$(INCLUDE_ROOT) -type d)
INCLUDES = $(addprefix -I, $(INCLUDE_DIRS))

# ソースファイルの自動検出
SRC_DIR = $(PROJECT_ROOT)/$(SRC_ROOT)
SRC = $(shell find $(SRC_DIR) -type f -name "*.cpp")

# オブジェクトファイルのリストを生成
OBJ_DIR = $(PROJECT_ROOT)/$(OBJ_ROOT)
OBJ = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SRC))

# デフォルトのターゲット
all: $(TARGET)

# 実行ファイルの生成ルール
$(TARGET): $(OBJ)
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -o $@ $^

# オブジェクトファイルの生成ルール
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDES)

# クリーンアップ
clean:
	rm -f $(OBJ) $(TARGET)

tasks.json

{
    "tasks": [
        {
            "type": "shell",
            "label": "C/C++: g++ build with makefile (debug)",
            "command": "make",
            "args": ["-f", "makefile_d"],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Runs the Makefile to build the project (debug).",
            "problemMatcher": "$gcc"
        },
        {
            "type": "shell",
            "label": "C/C++: g++ build with makefile (release)",
            "command": "make",
            "args": [],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Runs the Makefile to build the project (release).",
            "problemMatcher": "$gcc"
        }
    ],
    "version": "2.0.0"
}

launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C/C++: g++ build and debug with Makefile (debug)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/debug/bin/hello_world_d",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "preLaunchTask": "C/C++: g++ build with makefile (debug)",
            "miDebuggerPath": "/usr/bin/gdb"
        },       
        {
            "name": "C/C++: g++ build with makefile (release)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/release/bin/hello_world",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "preLaunchTask": "C/C++: g++ build with makefile (release)",
        }
    ]
}


いいなと思ったら応援しよう!