---
# System prepended metadata

title: Shell、CMake、Makefile
tags: [C語言]

---

{%hackmd BJrTq20hE %}
<style>
.markdown-body pre {
  background-color: #1e1e1e !important;
  border: 1px solid #555 !important;
  color: #dfdfdf;
}
</style>
###### tags: `C語言`
<style>
.red {
  color: #F12D16;
}
.orange {
  color: orange;
}    
.yellow {
  color: #EBF116;
}
.green {
  color: #1FC53B  ;
}
.cyan {
  color: #24DFDB;
}
.blue {
  color: #1FB0EA  ;
}
.purple {
  color: #6B6FE4;
}
.pink {
  color: pink;
}
</style>
# Shell、CMake、Makefile
## Shell [參考指令](https://kknews.cc/zh-tw/code/8zm9mjn.html)
### shell參數
```C=
mkdir -p f1/f2/f3  若存在f1則會在子目路創建f2，f2裡面創f3
cmd >> file //把cmd命令的輸出重定向到文件file中，如果file已經存在，則把信息加在原有文件後面。
alias L="ls -al" //把ls -al 這功能改名為L
```
### 執行shell script [參考](http://guang.logdown.com/posts/235513-unix-shell-source-vs-sh)
- ```source filename.sh``` 將script運行到現有的bash process
- ```sh filename.sh``` folk新的sub process來運行script，所以變數在執行完後就會被drop掉
```html=
#oaienv#
export OPENAIR_HOME=$(pwd)
export OPENAIR_DIR=$(pwd)
export OPENAIR1_DIR=$OPENAIR_HOME/openair1
#//////////////////////////////////////
abby@ubuntu:~/Documents/GitLab/oai_v2$ source oaienv
abby@ubuntu:~/Documents/GitLab/oai_v2$ echo $OPENAIR_HOME
/home/abby/Documents/GitLab/ #HOME路徑
abby@ubuntu:~/Documents/GitLab/oai_v2$ echo $OPENAIR1_DIR
/home/abby/Documents/GitLab/oai_v2/openair1 #DIR路徑
```
## gtest [範例參考](https://shengyu7697.github.io/googletest/)
```c=
TEST(TestCase, TestMethod){
    
}
```
### 正規表示式(Regular Expression) [參考](https://ithelp.ithome.com.tw/articles/10222163)
- 想要寫好測試，不論ctest或是gtest都需要了解正規表示式(RE)

## ctest [code](https://opensource.com/article/22/1/unit-testing-googletest-ctest)
```CMake=
首先，CMake順序先啟動測試 enable_testing() 其次，add_test(...) 接下來，set_tests_properties(...)
```
### 觀念說明
- GTest 是用於編寫單元測試的framework(框架)，CTest 則是用於運行它們的工具
- CTest 是 CMake 自帶的測試工具，它可以用於管理和運行已經編譯好的測試程序（可執行文件）
- [參考](https://google.github.io/googletest/quickstart-cmake.html)來了解如何透過CMake來啟動GoogleTest:
- 每次在Cmake上如果有更改測試參數，記得重新cmake 再來make編譯一次！


### <span class="orange">enable_testing() [參考](https://matgomes.com/cmake-ctest-to-add-cpp-tests/)
#### 可以開啟項目的測試功能
- 啟用對此目錄及以下目錄的測試。
- 你在`root CMakeLists.txt`最好就先使用 `enable_testing()` 如果在添加測試之前不包含對`enable_testing()`的調用，ctest將無法找到它們， 一個好的做法是在將任何測試子目錄包含在 CMakeLists.txt 中之前調用`enable_testing()`，該調用之後添加的所有測試都將被ctest發現！

###  <span class="orange">add_test() [參考](https://matgomes.com/cmake-ctest-to-add-cpp-tests/)
- 使用 add_test(...)將單個測試添加到CTest
- 如果只有執行`enable_testing()`而沒有添加任何測試用例（沒有調用 `add_test()`），則 CMake將啟用測試支持，但不會執行任何測試。

###  <span class="orange">set_tests_properties() [參考](https://codeantenna.com/a/oYj20C0iyZ)
    
    
### 實作說明: [參考程式碼](https://opensource.com/article/22/1/unit-testing-googletest-ctest)
```bash=
cd cpp_testing_sample
sudo cmake .
sudo make
./unit_test
```
- 如果我不執行`enable_testing()`，後面呼叫`add_test()`是不會執行的。
- 前面我們學到使用`enable_testing()`調用之後添加的所有測試都將被ctest發現，但如果你後續沒有使用`add_test()`，就算ctest找得到這些測試它也不會執行，可以想像成`add_test()`就像是一個執行測試的功能。
- 因為我們在檔案中加入重要的兩行，它會呼叫`gtest` 來產生一個`unit_test[1]_tests.cmake`，裡面包含可以用的測試屬性:
```Cmake=
include(GoogleTest) # Load and run CMake code from a file or module.
# Automatically add tests with CTest by querying the compiled test executable for available tests
gtest_discover_tests(unit_test) 
```

:::success
#### 重點整理:
- gtest是獨立的單元測試，你不需要跟主程式綁在一起，它用來測試程式開發的正確性，你可以把它想成Leetcode的測試平台。
- ctest則需要主程式的可執行檔，才能利用`add_test()`這種工具來檢查輸入參數與結果是否如同預期，用來測試輸出結果是否符合預期。
- 這就跟汽車外殼和汽車引擎的區別一樣，ctest是那個外殼。gtest是那個引擎。ctest只知道去執行某個可執行文件，它不管你裡面是什麼，你可以就打印個hello world也沒關係。
- 而gtest是用來生成測試的可執行文件的，因為我們手寫測試程序可能千奇百怪，不符合規範，因此可以藉助googletest來寫一個規範的測試。
:::
    
## CI/CD 教學[參考](https://opensource.com/article/22/2/setup-ci-pipeline-gitlab)
### install runner GitLab[影片教學](https://www.google.com/search?q=how+to+GitLab+Runner&rlz=1C1CHBF_zh-TWTW1010TW1010&oq=how+to+GitLab+Runner&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIKCAEQABgIGBMYHjIKCAIQABgIGBMYHjIKCAMQABgIGBMYHjIKCAQQABgIGBMYHjIKCAUQABgIGBMYHjIKCAYQABgIGBMYHjIKCAcQABgIGBMYHjIKCAgQABgIGBMYHjIKCAkQABgIGBMYHtIBCTI3MTlqMGoxNagCALACAA&sourceid=chrome&ie=UTF-8#fpstate=ive&vld=cid:011e3c68,vid:-CyVpfDQAG0)
- 當你在 GitLab 中配置 CI/CD 流水線（Pipeline）時，你會定義一系列的作業，例如構建代碼、運行測試、部署應用等。這些作業會被發送到 GitLab Runner，Runner 將這些作業在指定的運行環境中執行，最後將執行結果回傳到GitLab。
- GitLab 提供了一個方便的界面來註冊 Runner，但實際上Runner的執行是在你的機器上進行的。
  
### `.gitlab-ci.yml` [寫法參考](https://ithelp.ithome.com.tw/articles/10243647)
- 放在該專案的最上層位置，用來給GitLab建立pipline的時候用。
- YAML是一種人類可讀的數據序列化格式，它通常用於配置文件和數據交換的目的。
    
:::info
### [解決方法](https://forum.gitlab.com/t/solved-runner-configured-to-use-shell-executor-actually-running-docker/48025) Runner configured to use shell executor actually running docker
- Preparing the `shell` executor 在執行上卻變成 `docker+machine`
- 在GitLab->CI/CD -> Runner 裡面取消`Shared runners`
- `.gitlab-yml file` 裡面添加一個tag名稱，`GitLab Runner` 裡面tag也務必相同名稱。
:::
  
:::success
### GitLab出現未連結的錯誤[參考](https://stackoverflow.com/questions/67820925/gitlab-ci-cd-new-runner-has-not-been-connected-yet)
![](https://hackmd.io/_uploads/SJLwtUdA3.png)
- 通常是你剛創建一個runner沒有執行成功
```bash=
sudo gitlab-runner verify
sudo gitlab-runner start    
sudo gitlab-runner run
```
    
### pending問題
- 因為路徑跑到`/home/abby/.gitlab-runner/config.toml`而不是`/etc/gitlab-runner/config.toml`，移動到該位置後再次執行`sudo gitlab-runner run`
- 目前在虛擬機上面執行會突然卡住，暫時解法是不要開太久。
:::    
    
## CMake [完整教學](https://blog.csdn.net/Jqivin/article/details/117822412?spm=1001.2014.3001.5501) [簡短教學](https://chchwy.github.io/posts/2021-08-10-cmake-basics/)
### CMakeLists.txt
- 第三方函式庫 ```add_library()``` [動態與靜態連接](https://zh.m.wikibooks.org/zh-tw/CMake_%E5%85%A5%E9%96%80/%E5%BB%BA%E7%BD%AE%E8%88%87%E9%80%A3%E7%B5%90%E7%A8%8B%E5%BC%8F%E5%BA%AB)
- 設置環境變量[參考](https://blog.csdn.net/10km/article/details/51769633)
- cmake中對環境變量讀寫都是通過ENV前綴來訪問環境變量
讀取環境變量則要使用```$ENV{OPENAIR_DIR}```這樣的格式
- <span class ="pink">CMake盡量使用絕對路徑! 特別是對外部的.h .a .so的呼叫，不要用export 增加環境變數!</span>
- 若真要用相對路徑，建議用```set(MY_PAT /home/xxxx/path/xxxx)```取代```export```
![](https://i.imgur.com/sq0vino.png)


### <span class="orange">set( )
目的是將多個檔案綁定在一個變數名稱身上，有利於後續add_library()中加入該變數，增加可讀性。
```java=
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) //設置lib輸出位置
set(PHY_NR_SRC   //設置PHY_NR_SRC這個變量
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_dci_tools.c
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_dlsch.c
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_dlsch_tools.c
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_dlsch_coding.c
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_decoding.c
  ${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch.c
add_library(PHY_NR ${PHY_NR_SRC}) //一次把所有檔案加進來!!
// 上述的寫法預設產生靜態.a檔 => libPHY_NR.a
```
### <span class="orange">add_library( ) [參考](https://zh.m.wikibooks.org/zh-tw/CMake_%E5%85%A5%E9%96%80/%E5%BB%BA%E7%BD%AE%E8%88%87%E9%80%A3%E7%B5%90%E7%A8%8B%E5%BC%8F%E5%BA%AB)
添加函式庫(靜態、動態、共享)，把函式打包成編譯好的二進位制檔案，加上標頭檔案就可以供別人使用，不指定則預設是產生.a檔
```j=
//將上方set()的變量加入add_library，全部打包成函式庫
add_library(PHY_NR ${PHY_NR_SRC}) 
// add_library()示範:
add_library(ldpc_orig STATIC ${PHY_LDPC_ORIG_SRC}) //.a
add_library(ldpc_orig SHARED ${PHY_LDPC_ORIG_SRC}) //.so
add_library(ldpc_orig MODULE ${PHY_LDPC_ORIG_SRC}) //.so
```
### <span class="orange">add_dependencies( ) [參考](https://blog.csdn.net/KingOfMyHeart/article/details/112983922)
跟編譯器說這個lib跟我的main.c是有關連的，很多時候lib加入之前就被編譯器認為找不到連結，所以使用這函式來解決！ex: ```/usr/bin/ld: cannot find -lacc100_driver```
```java=
add_executable(MAIN main.c) //執行編譯
add_dependencies(MAIN a.so b.so) //加入相依性
target_link_libraries(MAIN a.so b.so c.so) //連結動態庫  
```
    
### <span class="orange">函式庫連結: link_libraries( )、link_directories( ) [參考](https://www.jianshu.com/p/54292d374584)
#### 情境:用於你在外部產生的lib檔，除非特殊情況，不然檔案都會在CMakeLists.txt中完成，所以較少使用的方法。

![](https://i.imgur.com/zCjpYpH.png)
    
<span class="pink">法1 link_libraries(指定我要連結哪個lib檔)
- 設定特殊的lib檔連結路徑+檔名，通常是連外部的(.so)(.a)
- 有時候法1連結不到(```undefined reference to ...```)，可以使用法2
```java=
link_libraries(${OPENAIR1_DIR}/PHY/BF/libAN.so) //包含檔名
```
<span class="pink">法2 link_directories(指定我要連結lib的目錄)
- 設定特殊的lib檔案路徑
- 在target_link_libraries中加入此libAN.so
```m=
link_directories(${OPENAIR1_DIR}/PHY/BF) //指定目錄位置
target_link_libraries(nr_ulsim AN) //記得libAN.so就是AN
```    
### <span class="orange">add_executable( )
把執行檔有關的檔案加入這個函式中
```java=
add_executable(nr_ulsim //產生nr_ulsim的可執行檔
  ${OPENAIR1_DIR}/SIMULATION/NR_PHY/ulsim.c
  ${OPENAIR_DIR}/common/utils/threadPool/thread-pool.c
  ${OPENAIR_DIR}/common/utils/utils.c
  ${OPENAIR_DIR}/common/utils/system.c
  ${OPENAIR_DIR}/common/utils/nr/nr_common.c
  ${OPENAIR_DIR}/executables/softmodem-common.c
  )   
```

### <span class="orange">include_directories( ) [參考](https://zh.m.wikibooks.org/zh-tw/CMake_%E5%85%A5%E9%96%80/%E5%BB%BA%E7%BD%AE%E8%88%87%E9%80%A3%E7%B5%90%E7%A8%8B%E5%BC%8F%E5%BA%AB)、[參考2](https://ubuntuqa.com/zh-tw/article/9081.html)
設定include的路徑，也就是.h檔的路徑
```java=
include_directories(${PROJECT_SOURCE_DIR}/src) //可以用這種方式
include_directories(/home/abby/Documents/C/practice/Gravity/include) //也可以用此方式
```
    
### <span class="orange">target_link_libraries( ) 
- 連結函式庫(.a .so)與程式main.o檔，假如有一個```libhello.a```，則對應的寫法就是小寫```hello```!
- CMake、Makefile中一般默認的lib 的加載路徑是/lib /usr/lib 如果想要改變程序運行時的libs的加載路徑就需要用到```-Wl``` , rpath
- 連結的過程會依照順序從左到右開始，所以錯誤的依賴順序關係會導致編譯失敗，因此有了
```--start-group archives --end-group```的用法，包在裡面的lib不會理會依賴關係，唯一的缺點是編譯會變久->But, who care? 複雜的連結都建議使用這個方法。
- 小知識:target對象只要一樣，分幾段都可以使用!
```java=
//以下4種連接方式都可以成功執行
target_link_libraries(MAIN hello PHY_NR) //1
target_link_libraries(MAIN -lhello)      //2
target_link_libraries(MAIN -Wl,-lhello)) //3
target_link_libraries(-Wl,--start-group -Wl,-lrte_kvargs -Wl,--end-group) //4
```
:::success
#### ```-Wl,--whole-archive <libxxx.a> -Wl,--no-whole-archive``` [參考](http://litaotju.github.io/c++/2020/07/24/Whole-Archive-in-static-lib/)
- 可以強制將緊接其後的程式庫全部都連結進來，不管個別 object 是否實際被使用到。遇到 --no-whole-archive 之後的程式庫又會以「正常」方式連結，由於這個方式不分青紅皂白把所有object 都連結進來，目標檔很可能會變得肥大。
- 在參考連結可以看到，如果在.h檔做class初始化，由於main函式沒有使用到B的函式，編譯器預設不會把用不到的目標文件納入編譯內容，導致class B沒有初始化，這種在執行上都不會有任何Error或Warning警訊```undefined reference to ....```，但這在複雜的大型專案中會是個致命失誤！
```bash=
g++ main.cpp b.cpp a.cpp #output B::B  A::A
g++ -o run main.o libX.a #output A::A
g++ -o run2 main.o -Wl,--whole-archive libX.a -Wl,--no-whole-archive #output B::B  A::A
g++ -o run3 main.o -Wl,-start-group libX.a -Wl,-end-group #output A::A
```
:::
    
    
### C、Cpp混合編譯範例[參考教學](https://blog.csdn.net/weixin_43955214/article/details/104335415?spm=1001.2101.3001.4242.1&utm_relevant_index=3)
- 在cpp檔中有使用include其他函式宣告的標頭檔記得要用```extern "C" {}```不然會出現```undefined reference to xxxx()```的錯誤。
- 如果在程式有用到File IO讀寫記得把路徑改成絕對路徑，或許平常單元練習可以寫相對路徑，但在Cmake架構下會發生```Segmentation Fault```。
```c=
FILE *fp_excel = NULL;
fp_excel = fopen("/home/isip/minhsun/abby/Document/c/cmake/data/2D_MVDR_dB.csv", "w"); 
```
- CMake本身可以混合編譯，所以不用指定如何編譯，但是要給Flag編譯條件！
```m=
set(CMAKE_C_FLAGS "-mavx512f") #Defined C
set(CMAKE_CXX_FLAGS "-mavx512f -std=c++14") #Defined C++
```

:::info
#### ```error: ld return 1 exit status```
![](https://i.imgur.com/B4LiGKF.png)

若編譯一個cpp檔的```.a``` or ```.so```，一直發生link失敗的錯誤時，除了嘗試過上述所有方法外，最後一招就是額外寫一個```CMakeLists.txt```專門產生這些```.cpp```的lib，之後在原版的```CMakeLists.txt```直接連接這個檔案即可，有時候是原版cmake在編譯cpp的時候有.o連結的衝突或是其他變數不可預期的干涉，所以在外部建立函式庫在link進去也是個解決方法！
:::
    
### Example
```m=
cmake_minimum_required(VERSION 3.0.0) # CMake version minimum request is 3.0.0
project(compile) # give project a name!

set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_C_FLAGS " -mavx512f")
set(CMAKE_CXX_FLAGS "-mavx512f -std=c++14")

set(THIRD_PARTY_SRC ${PROJECT_SOURCE_DIR}/third_party) # define where is THIRD_PARTY_SRC path
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) # set library path
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # set executable path

add_library(THIRD_PARTY ${THIRD_PARTY_SRC}/def_gravity.c) # create third party library(THIRD_PARTY)
add_library(MATH_FUNCTION ${THIRD_PARTY_SRC}/math_func.cpp) # create static library(Cpp file)

include_directories(/home/isip/minhsun/abby/Document/c/cmake/include) # Include (.h) file path

add_executable(TEST c_avx_version.c # need to compiled file
    ${PROJECT_SOURCE_DIR}/src/one.c # one.c
    ${PROJECT_SOURCE_DIR}/src/two.c # two.c
    ${PROJECT_SOURCE_DIR}/src/three.c # three.c
    )    
target_link_libraries(TEST THIRD_PARTY MATH_FUNCTION m) # Connect TEST, MATH_FUNCTION and THIRD_PARTY library . Also math.h needs to be included!

```
    
    
### 程式執行流程
![](https://i.imgur.com/rSlWPz1.png)
    
```j=
cmake_minimum_required(VERSION 3.16)
project(MyProject)          // 專案名稱
set(CMAKE_CXX_STANDARD 11)  // 要求 C++11 標準
add_executable(MyApp main.c work.c)    // 執行檔和原始碼[有main主函式]
#//////////////////////////////////////////////////
add_library(flapp_sample SHARED //建立共享函式庫
  ${OPENAIR2_DIR}/ENB_APP/flexran_apps/sample.c
)
add_library(ldpc MODULE         //建立動態函庫
  ${PHY_LDPC_OPTIM8SEGMULTI_SRC} )
#//////////////////////////////////////////////////
include_directories("C:/path/to/include")  // 設定 include 目錄
target_link_libraries(MyApp "C:/path/to/lib") // 設定 lib 連結
```

## Makefile [語法參考](https://iter01.com/600516.html)、[Makefile與cmake語法比較](http://gitqwerty777.github.io/cmake/#Makefile-%E5%92%8C-CMake-%E7%9A%84%E8%AA%9E%E6%B3%95%E6%AF%94%E8%BC%83)
![](https://i.imgur.com/NINa1wL.png)

#### 在大型專案的Makefil加入想印出的資訊
```make=
arbitrary_variable = $(shell echo helloworld)
$(info $(arbitrary_variable)) 
```
    
### Makefile 版型 [參考](https://makefiletutorial.com/#running-the-examples)
- 使用前先在Makefile的位置創建一個```src```的資料夾，把所有會用到的```.c```、```.cpp```、```.h```放入。
```Makefile=
# Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
TARGET_EXEC := final_program

BUILD_DIR := ./build
SRC_DIRS := ./src

# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')

# String substitution for every C/C++ file.
# As an example, hello.cpp turns into ./build/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)

# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)

# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP

# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
	$(CC) $(OBJS) -o $@ $(LDFLAGS)

# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
	mkdir -p $(dir $@)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@


.PHONY: clean
clean:
	rm -r $(BUILD_DIR)

# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS) 
```

### Makefile實作練習 [參考](http://wen00072.github.io/blog/2014/03/12/makefile-automatically-assign-a-file-to-a-specific-directory/)
    
    
# 實作檔案編譯 [Git](https://gitlab.com/abby88771/cmake.git)
## 流程
![](https://i.imgur.com/vrp9lbN.jpg =50%x)![](https://i.imgur.com/OmOQjSP.jpg =50%x)

```shell=
$source env_set  #執行env_set環境變數
$cd build        #進入build資料夾
$sudo cmake ..   #讀取 CMakeLists.txt 產生Makefile
$sudo make       #make會去執行Makefile，生成TEST執行檔
$cd ..
$cd bin          #進入TEST的位置
./TEST
```
    
```bash=
Command 'sudo' is available in '/usr/bin/sudo'
The command could not be located because '/usr/bin' is not included in the PATH environment variable.
sudo: command not found
# sol:在/build裡面輸入這個
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin #之後再試一次
# sol有時候是source後的資料亂掉!
```