linux下使用vscode搭建C++開發環境


最近在linux上跑一些開源庫做學習用, 順手就搭了一下vscode的c++開發環境, 這里分享一下vscode進行C++開發的基本環境結構.

1. 首先是編輯器, vscode直接官網下載的, 后期可以用 apt 直接更新, 個人覺得還是挺方便的, 有喜歡折騰的小伙伴可以去github上拉開源版本的下來自己編譯, 這里不過多贅述

2. 其次是編譯器, 我使用的是GNU編譯器g++, 生成腳本我選擇了makefile

以上是基礎工具, 如果把vscode換成vim + shell腳本, 調試直接gdb的話, 就基本上是原生環境開發了

 

接下來就是開發環境的搭建了, 這里我先整理一下一個工程量稍微大一些的項目所應該包含的項目種類, 再根據整理的結果給出一個我寫的例子, 之后再對該例進行不斷完善

對於一個大型工程來說, 可能至少會包含以下幾種不同的工程:

1. 可執行程序 : 即項目主要的目標

2. 靜態庫 : 集成一些基礎的工具函數和一些基礎功能的封裝

3. 動態庫 : 作為插件, 非核心功能之類的東西

4. 資源文件 : 各種圖片, 文件, 音頻, xml等等

以上是我認為的一個工程量稍大的程序可能會包含的項目種類, 根據上面這四類, 我構建了如下的文件結構 :

.
├── debug
├── lib
├── project
│   ├── debug.makefile
│   ├── exe_test
│   │   ├── compile
│   │   ├── .d
│   │   ├── header
│   │   │   └── test.h
│   │   ├── makefile
│   │   └── src
│   │       └── test.cpp
│   ├── lib_a
│   │   ├── compile
│   │   ├── .d
│   │   ├── header
│   │   │   ├── a_1st.h
│   │   │   ├── a_2nd.h
│   │   │   └── a_3rd.h
│   │   ├── makefile
│   │   └── src
│   │       ├── a_1st.cpp
│   │       ├── a_2nd.cpp
│   │       └── a_3rd.cpp
│   ├── lib_so
│   │   ├── compile
│   │   ├── .d
│   │   ├── header
│   │   │   ├── so_1st.h
│   │   │   ├── so_2nd.h
│   │   │   └── so_3rd.h
│   │   ├── makefile
│   │   └── src
│   │       ├── so_1st.cpp
│   │       ├── so_2nd.cpp
│   │       └── so_3rd.cpp
│   └── makefile
├── release
└── .vscode
    ├── c_cpp_properties.json
    ├── launch.json
    ├── settings.json
    └── tasks.json

20 directories, 23 files

在當前項目目錄下共有4個子目錄和一個vscode專用的隱藏目錄 :

1. debug : 所有我們生成的debug版本的可執行程序以及debug版本程序所需的資源都會生成在這個目錄中

2. release : 同上, 但可執行程序和資源文件都是release版的

3. lib : 所有動態庫, 靜態庫會生成在這個目錄中, debug版和release版用文件名結尾是否帶 D 來區分

4. project : 所有當前項目相關的工程都在這個目錄中

5. .vscode : vscode專用目錄, 其中包含了當前項目相關的vscode配置信息

下面再看一下project目錄, 該目錄下共有3個項目目錄和兩個makefile :

1. lib_a : 該項目最終會生成一個靜態庫供程序使用

2. lib_so : 該項目最終會生成一個動態庫供程序使用

3. exe_test : 該項目最終會生成一個可執行程序, 該程序會使用到上述靜態庫和動態庫

4. 兩個makefile用於控制所有項目的debug版, release版生成

最后再解析一下每一個項目目錄, 每個項目都包含了4個子目錄和一個makefile :

1. src : 所有的源文件放置在該目錄中

2. header : 所有的頭文件放置在該目錄中

3. compile : 編譯后的.o文件會在這個目錄中生成

4. .d : 該目錄用於存放每個源文件的依賴關系

5. makefile : 該makefile控制當前項目的生成

以上是例子文件結構的大概說明, 下面我們就這個例子進行完善, 針對每一個工程和整個項目, 編寫makefile, 完成代碼的編譯生成

 

首先針對整個項目, 我們要生成每一個工程, 並保證工程的生成順序符合每個工程間的依賴關系

這里先看一下project/makefile, 這個makefile用於生成所有工程release版本

 1 export BUILD_VER := RELEASE
 2 export CXXFLAGS := -Wall -std=c++11
 3 export RM := rm -f
 4 
 5 .PHONY:build_all clean_all clean_all_cache
 6 
 7 build_all:
 8     cd ./lib_so && make
 9     cd ./lib_a && make
10     cd ./exe_test && make
11 
12 clean_all:
13     cd ./lib_so && make clean
14     cd ./lib_a && make clean 
15     cd ./exe_test && make clean
16 
17 clean_all_cache:
18     cd ./lib_so && make clean_cache
19     cd ./lib_a && make clean_cache
20     cd ./exe_test && make clean_cache

該makefile首先會覆寫3個變量, 並將變量導出成為全局變量, 其中BUILD_VER用於控制生成程序的版本, 緊隨其后的是3個偽目標, 分別用於生成每個工程, 清理所有生成文件以及清理生成過程中的產生的.o和.d

接下來再來看project/debug.makefile, 這個makefile用於生成所有工程的debug版本

1 include ./makefile
2 
3 BUILD_VER := DEBUG

該makefile引入release版的makefile, 並修改BUILD_VER為DEBUG, 該makefile名稱不是make能夠自動識別的名稱, 使用需要加上 -f 參數, 如 : make -f debug.makefile

通過上面兩個makefile, 我們基本完成了對代碼生成的版本控制和整個項目的生成流程, 下面只需要針對每一個工程, 編寫對應的makefile即可

下面是3個工程的makefile :

首先是靜態庫工程lib_a

 1 vpath %.cpp ./src
 2 vpath %.h ./header
 3 
 4 .PHONY: all clean clean_cache
 5 all : # 默認目標
 6 
 7 CXXINCLUDES = -I ./header
 8 ARFLAGS = -rcs
 9 SRCS_WITH_PATH = $(wildcard ./src/*.cpp)
10 SRCS = $(SRCS_WITH_PATH:./src/%.cpp=%.cpp)
11 DEPS = $(SRCS:.cpp=.d)
12 DEPS_WITH_PATH = $(SRCS:%.cpp=./.d/%.d)
13 OBJS = $(SRCS:.cpp=.o)
14 OBJS_WITH_PATH = $(SRCS:%.cpp=./compile/%.o)
15 TARGET_NAME = tsi.a
16 OUTDIR = ../../lib/
17 
18 ifeq ($(BUILD_VER), DEBUG)
19 CXXFLAGS += -g3
20 TARGET_NAME := tsiD.a
21 endif
22 
23 ifeq ($(BUILD_VER), RELEASE)
24 CXXFLAGS += -O2
25 endif
26 
27 #生成依賴關系,保證修改.h時也會重新編譯相關.cpp
28 -include $(DEPS)
29 
30 %.d:$(SRCS)
31     @set -e;\
32     $(RM) $@;\
33     $(CXX) $(CXXINCLUDES) -MM $< > .d/$@;
34 
35 %.o:%.cpp
36     $(CXX) $(CXXFLAGS) $(CXXINCLUDES) -c $< -o ./compile/$@
37 
38 all:$(TARGET_NAME)
39 
40 $(TARGET_NAME):$(OBJS)
41     $(AR) $(ARFLAGS) $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH)
42 
43 clean:
44     $(RM) $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)
45 
46 clean_cache:
47     $(RM) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)

makefile中首先讀取了當前工程下的兩個目錄, 保證正確搜索.h和.cpp之后聲明三個偽目標, 並以all為終極目標, 之后聲明了一系列變量, 這里詳細解釋一下每一個變量, 跟大家解釋一下我的思路

CXXINCLUDES : 該變量包含了生成時c++的包含目錄

ARFLAGS : 靜態庫打包標志

SRCS_WITH_PATH : 包含路徑的所有源文件, 該寫法可以自動匹配指定目錄下的所有.cpp, 大型工程中可能會有很多源文件, 每次更新刪除都要修改makefile的話會很不方便

SRCS : 剔除所有源文件的前綴路徑

DEPS : 對每一個源文件, 生成一個對應的寫有依賴關系的.d文件

DEPS_WITH_PATH : 包含前綴路徑的全部.d文件

OBJS : 源文件編譯生成的全部.o文件

OBJS_WITH_PATH : 包含前綴路徑的全部.o文件

TARGET_NAME : 生成目標的名稱

OUTDIR : 輸出目錄

 

在聲明了以上這些變量之后, 通過對全局變量BUILD_VER的值的判斷, 在CXXFLAGS里添加不同的參數以控制版本, 並對文件名等信息做修改

接下來我用-include讓當前makefile讀取所有.d依賴關系, 當前文件由於沒有找到這些.d文件, 會在文件中搜索有無生成的靜態目標, 這時, make會搜索到下方的%.d:$(SRCS)

根據該靜態目標, .d文件便會被生成出來並被加載

假設我們當前指明生成的是偽目標all

all所依賴的目標是我們指定的文件名$(TARGET_NAME), 該變量所指向的目標又依賴於所有的.o文件, 由於.o文件沒有被生成, make又會搜索並調用靜態目標%.o:%.cpp進行.o文件的生成

在生成完所有的.o文件之后, 目標$(TARGET_NAME)才會被執行, 最終在../../lib目錄中生成tsi.a或tsiD.a

理解了上面的內容之后, 接下來兩個工程 : 動態庫以及可執行文件的makefile基本也可以套用上面的內容再進行修改得到, 這里我貼出我的寫法供大家參考

動態庫makefile

 1 vpath %.cpp ./src
 2 vpath %.h ./header
 3 
 4 .PHONY: all clean clean_cache
 5 all : # 默認目標
 6 
 7 CXXFLAGS += -fPIC
 8 CXXINCLUDES = -I ./header
 9 SRCS_WITH_PATH = $(wildcard ./src/*.cpp)
10 SRCS = $(SRCS_WITH_PATH:./src/%.cpp=%.cpp)
11 DEPS = $(SRCS:.cpp=.d)
12 DEPS_WITH_PATH = $(SRCS:%.cpp=./.d/%.d)
13 OBJS = $(SRCS:.cpp=.o)
14 OBJS_WITH_PATH = $(SRCS:%.cpp=./compile/%.o)
15 TARGET_NAME = libtest.so
16 OUTDIR = ../../lib/
17 
18 ifeq ($(BUILD_VER), DEBUG)
19 CXXFLAGS += -g3
20 TARGET_NAME := libtestD.so
21 endif
22 
23 ifeq ($(BUILD_VER), RELEASE)
24 CXXFLAGS += -O2
25 endif
26 
27 #生成依賴關系,保證修改.h時也會重新編譯相關.cpp
28 -include $(DEPS)
29 
30 %.d:$(SRCS)
31     @set -e;\
32     $(RM) $@;\
33     $(CXX) $(CXXINCLUDES) -MM $< > .d/$@;
34 
35 %.o:%.cpp
36     $(CXX) $(CXXFLAGS) $(CXXINCLUDES) -c $< -o ./compile/$@
37 
38 all:$(TARGET_NAME)
39 
40 $(TARGET_NAME):$(OBJS)
41     $(CXX) -shared -o $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH)
42 
43 clean:
44     $(RM) $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)
45 
46 clean_cache:
47     $(RM) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)

可執行程序makefile

 1 vpath %.cpp ./src
 2 vpath %.h ./header
 3 
 4 .PHONY: all clean clean_cache
 5 all : # 默認目標
 6 
 7 CXXINCLUDES = -I ./header -I ../lib_a/header -I ../lib_so/header
 8 SRCS_WITH_PATH = $(wildcard ./src/*.cpp)
 9 SRCS = $(SRCS_WITH_PATH:./src/%.cpp=%.cpp)
10 DEPS = $(SRCS:.cpp=.d)
11 DEPS_WITH_PATH = $(SRCS:%.cpp=./.d/%.d)
12 OBJS = $(SRCS:.cpp=.o)
13 OBJS_WITH_PATH = $(SRCS:%.cpp=./compile/%.o)
14 LINK_LIB = ../../lib/tsi.a
15 LINK_USR_SO = -L ../../lib -Wl,-rpath=../lib -ltest
16 TARGET_NAME = test
17 OUTDIR = ../../release/
18 
19 ifeq ($(BUILD_VER), DEBUG)
20 CXXFLAGS += -g3
21 LINK_LIB := ../../lib/tsiD.a
22 LINK_USR_SO := -L ../../lib -Wl,-rpath=../lib -ltestD
23 TARGET_NAME := testD
24 OUTDIR := ../../debug/
25 endif
26 
27 ifeq ($(BUILD_VER), RELEASE)
28 CXXFLAGS += -O2
29 endif
30 
31 #生成依賴關系,保證修改.h時也會重新編譯相關.cpp
32 -include $(DEPS)
33 
34 %.d:$(SRCS)
35     @set -e;\
36     $(RM) $@;\
37     $(CXX) $(CXXINCLUDES) -MM $< > .d/$@;
38 
39 %.o:%.cpp
40     $(CXX) $(CXXFLAGS) $(CXXINCLUDES) -c $< -o ./compile/$@
41 
42 all:$(TARGET_NAME)
43 
44 $(TARGET_NAME):$(OBJS)
45     $(CXX) -o $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH) $(LINK_LIB) $(LINK_USR_SO)
46 
47 clean:
48     $(RM) $(OUTDIR)$(TARGET_NAME) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)
49 
50 clean_cache:
51     $(RM) $(OBJS_WITH_PATH) $(DEPS_WITH_PATH)

這里有幾點需要注意的是, 在可執行程序鏈接時, 我用-Wl,-rpath指定了程序執行時回去何處尋找libtest.so這個動態庫, 如果不想這樣寫, 需要指定動態庫生成到系統默認加載的路徑中去, 比如/usr/lib, 同樣程序在其他機器上部署時也需要做同樣的操作

另外就是關於.d依賴生成我使用的參數是-MM, 因為GNU編譯器如果使用-M參數會自動加入一些其它的依賴關系, 具體內容可以用g++ -M xxx.cpp做簡單驗證, 如下圖:

-MM:

 -M(后面還有....):

 

在完成了上述步驟之后, 我們的項目其實已經可以正常編譯生成執行了, 只是跟vscode沒什么聯系, 這里我們先在project目錄中運行make, make clean_all, make, make clean_all_cache來看一下辛苦編寫makefile的成果

 

 很成功, 舒服了

接下來, 為了做到一鍵運行(F5)或者一鍵debug調試, 我們要對vscode進行項目配置, 這里我們要修改.vscode目錄下的三個文件:launch.json task.json c_cpp_properties.json

在此之前先貼一下我在vscode中安裝的插件, 這些插件能讓開發環境更美觀, 代碼編寫更順暢

 其中C/C++提供了只能高亮頭文件查找等功能, Chinese是一些選項的漢化, Font Switcher可以快速更換字體(這個無所謂...), One Dark Pro是一款比較美觀的主題配色, Python(個人需求, 寫一些簡單的腳本還是很方便的), TabOut可以針對各種括號按tab快速跳出, vscode-icons美化各種圖標

下面到了vscode的啟動配置, 在vscode的運行選項卡中, 我們可以選擇當前項目啟動的配置, 該配置集由launch.json來控制, 這里我先貼出我的launch.json, 再進行詳細說明

 1 {
 2   "configurations": [
 3     {
 4       "name": "run release",
 5       "type": "cppdbg",
 6       "request": "launch",
 7       "program": "${workspaceFolder}/release/test",
 8       "args": ["-r", "-debug"],
 9       "stopAtEntry": false,
10       "cwd": "${workspaceFolder}/release",
11       "environment": [],
12       "externalConsole": false,
13       "MIMode": "gdb",
14       "setupCommands": [
15           {
16               "description": "為 gdb 啟用整齊打印",
17               "text": "-enable-pretty-printing",
18               "ignoreFailures": true
19           }
20       ],
21       "preLaunchTask": "make release"
22     },
23     {
24       "name": "run debug",
25       "type": "cppdbg",
26       "request": "launch",
27       "program": "${workspaceFolder}/debug/testD",
28       "args": ["-r", "-debug"],
29       "stopAtEntry": true,
30       "cwd": "${workspaceFolder}/debug",
31       "environment": [],
32       "externalConsole": false,
33       "MIMode": "gdb",
34       "setupCommands": [
35         {
36             "description": "為 gdb 啟用整齊打印",
37             "text": "-enable-pretty-printing",
38             "ignoreFailures": true
39         }
40       ],
41       "preLaunchTask": "make debug"
42     }
43   ]
44 }

這里我配置了兩個啟動選項, 一個直接運行release程序, 另一個運行debug程序, 這里針對debug啟動項進行解釋說明

name : 我們在啟動選項卡里看到的啟動項名稱

type : cppdbg就可以, 具體可以查閱vscode官方說明

request : 啟動項類型, 一種是附加程序一種是直接啟動, 這里是直接啟動

program : 啟動程序路徑, 在vscode里打開的根目錄即為${workspaceFolder}, 后面加上release路徑

args : 傳入程序的參數

stopAtEntry : 程序是否自動在入口暫停, debug版才有用哦

cwd : 程序運行時的目錄

environment :要添加到程序環境中的環境變量, 具體可以查閱vscode官方說明, 這里我直接沒填

externalConsole : 選擇程序是在新的控制台中啟動還是在集成控制台啟動

MIMode : 調試器選擇

setupCommands : vscode官方文檔查, 這里我是直接用默認配置的

preLaunchTask : 這個是最重要的選項了, 該選項指明了在運行當前選項卡之前要運行的task任務, 這個task任務配置在同目錄下的tasks.json中, 這里填的內容是task的label

 

為了解釋preLaunchTask這個選項, 我們引入tasks.json, 這里貼出我的tasks.json, 進行說明

 1 {
 2   "version": "2.0.0",
 3   "tasks": [
 4     {
 5       "type": "shell",
 6       "label": "make release",
 7       "command": "make",
 8       "args": [],
 9       "options": {
10         "cwd": "${workspaceFolder}/project"
11       },
12       "group": "build"
13     },
14     {
15       "type": "shell",
16       "label": "make debug",
17       "command": "make -f debug.makefile",
18       "args": [],
19       "options": {
20         "cwd": "${workspaceFolder}/project"
21       },
22       "group": "build"
23     },
24     {
25       "type": "shell",
26       "label": "make clean",
27       "command": "make",
28       "args": ["clean_all"],
29       "options": {
30         "cwd": "${workspaceFolder}/project"
31       },
32       "group": "build"
33     },
34     {
35       "type": "shell",
36       "label": "make clean debug",
37       "command": "make -f debug.makefile",
38       "args": ["clean_all"],
39       "options": {
40         "cwd": "${workspaceFolder}/project"
41       },
42       "group": "build"
43     },
44     {
45       "type": "shell",
46       "label": "make clean cache",
47       "command": "make",
48       "args": ["clean_all_cache"],
49       "options": {
50         "cwd": "${workspaceFolder}/project"
51       },
52       "group": "build"
53     }
54   ]
55 }

在這個文件中我配置了5個task, 其中前2個task : make release 和 make debug用於執行不同的makefile

這里我針對make debug做個簡單說明

type : task的類型, 這里填shell相當於執行shell命令

label : task的名字

command : 要執行的指令, 這里要注意 make -f xxx.file這種命令, -f xxx這個內容要直接寫到命令內容中, 而不能寫到下面的args里, 會無法識別, 這里大家可以自行驗證一下

args : command要附加的參數

options : 其他選項

cwd : task執行的目錄

group : task的分組, 可以查一下vscode官方說明

 

經過以上配置, vscode就和我們的makefile聯系在一起了, 選好啟動項f5就完事了, 這里我貼出我的test.cpp, test.h, 和vscode斷點調試運行截圖

 1 #include "test.h"
 2 
 3 int main(int argc, char* argv[])
 4 {
 5   if (argc > 0)
 6   {
 7     std::cout << "input param : ";
 8     for (int idx = 0; idx < argc; ++idx)
 9     {
10       std::cout << argv[idx] << " ";
11     }
12     std::cout << std::endl;
13   }
14 
15   std::cout << std::endl << "using a" << std::endl;
16   std::cout << tsi::a1st::lib_name() << std::endl
17             << tsi::a2nd::lib_author() << std::endl
18             << tsi::a3rd::lib_version() << std::endl;
19 
20   std::cout << std::endl << "using so" << std::endl;
21   std::cout << tsi::so1st::lib_name() << std::endl
22             << tsi::so2nd::lib_author() << std::endl
23             << tsi::so3rd::lib_version() << std::endl;
24   return 0;
25 }
 1 #ifndef _TSI_TEST_
 2 #define _TSI_TEST_
 3 
 4 #include <iostream>
 5 
 6 #include "a_1st.h"
 7 #include "a_2nd.h"
 8 #include "a_3rd.h"
 9 #include "so_1st.h"
10 #include "so_2nd.h"
11 #include "so_3rd.h"
12 
13 #endif

 

 

 

這樣, 一個簡單的項目工程的開發環境就搭建成功了. PS: 在調試時, 我遇到了一個小問題, 這里也貼一下

這里一開始我無法進行gdb調試, 提示無法讀取文件雲雲, 點擊創建后, 有了上述提示, 在網上檢索了一下, 只有解決方案, 沒有詳細解釋其中機理, 這里我先貼出解決辦法

在/目錄下建立build目錄,在該目錄中建立錯誤提示中對應的目錄, 並下載提示對應版本glibc到目錄中並解壓即可解決問題

關於該錯誤我認為是gdb調試加載路徑錯誤導致, 如果有了解詳細原因的朋友, 請務必留言指點, 在此謝過

 

以上, 上方示例只是一個簡單的項目結構, 其中一定還有很多不足之處, 本文僅起到一個拋磚引玉的作用, 如有錯誤疏漏, 請務必指出, 有問題歡迎討論, 轉載注明出處, 感謝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM