{%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)  - 通常是你剛創建一個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```  ### <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中完成,所以較少使用的方法。  <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```  若編譯一個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! ``` ### 程式執行流程  ```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)  #### 在大型專案的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) ## 流程  ```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後的資料亂掉! ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up