---
tags: SSSTC Summer Intern
---
# Cppcheck Applied on GitLab
---
## Cppcheck
在建興實習的這兩個月,我跟Skyler最主要的任務就是幫忙架一個叫作「Cppcheck」的測試平台。Cppcheck是一種用來分析C語言/C++在coding上的問題,因為有些語法雖然編譯得過,但可能會對整個程式造成一些潛在問題。
```c=
#include<stdio.h>
int main(){
int a;
int b;
int c = 1;
int d = 2;
b++;
c++;
d = 4;
return 0;
}
```
例如上述的程式碼,在編譯上沒有問題,但很明顯的有一些變數沒用到及變數未宣告。
<br>
## GitLab
稍微瞭解了Cppcheck後,接下來簡單介紹一下GitLab。GitLab是基於Git的軟體整合開發平台,且具有線上編輯、CI/CD等功能。其中CI/CD是GitLab內建的一款工具,CI(Continuous Integration)指的是持續整合,CD(Continuous Delivery)指的是持續交付。當開發者建立了CI/CD,那麼當開發者使用git commit,那麼就會觸發CI/CD相關的一系列操作,這一系列操作由Runner執行,相關組態寫在yml檔中,每一個Commit會發出一條Pipeline,Pipeline是不同Stage的Job的集合,同一Stage的Job以並列的方式執行,然後每個Stage之間是順序執行。
囉囉唆唆的說了一大堆,簡單來說,GitLab就是一棵大樹,有一個主幹叫作「Master」,然後每個人會建立一些分支叫作「Branch」,每個人先在自己的Branch完成自己的工作,然後再將Branch「Merge」到Master裡,完成分工合作。

<br>
## Application
有了Cppcheck這個套件和GitLab的基本知識後,我們就要將Cppcheck套用在Git上了。我和Skyler利用Git CI/CD的整合優勢分工合作,我們各自開了一個Branch,將我們要完成的Project在自己的Branch上完成,然後再整合起來。Skyler負責寫一個資料整理的Python檔,而我則負責寫GitLab Pipeline要執行的yml檔。
由於Cppcheck跑完整包Code後會產生許多資料,所以Skyler將所有輸出依照不同的檔案分類,也將不同的檢測類型分類,最後再Sumary起來。以下是他所寫的cppcheck_trans.py
```python=
import sys
class CppCheck:
def __init__(self):
self.printData = ["Line= ", "Column= ", "Check:", "Problem: "]
self.previous_file = "init"
# Count problems in dif folders
self.core_count = 0
self.hal_count = 0
self.host_count = 0
self.media_count = 0
self.others_count1 = 0
# Count num of different problems
self.style_count = 0
self.error_count = 0
self.note_count = 0
self.others_count2 = 0
def WriteCppcheckFile(self, fileInput, fileOutput):
lines = fileInput.readlines()
for count1, line in enumerate(lines):
if count1 % 3 == 2:
fileOutput.write("")
elif count1 % 3 == 1:
fileOutput.write(str(line))
else:
for count2, data1 in enumerate(line.split(':')):
if data1 == "nofile":
break
if count2 == 0:
write_r = ""
for count3, word in enumerate(data1.strip().split('/')):
if count3 == 0:
write_r = ("Location: ")
else:
write_r = write_r + ("/" + str(word))
# Count problems in dif folders
if count3 == 1:
if word == "Core":
self.core_count += 1
elif word == "Hal":
self.hal_count += 1
elif word == "Host":
self.host_count += 1
elif word == "Media":
self.media_count += 1
else:
self.others_count1 += 1
# If dif file, print location
if word != self.previous_file:
fileOutput.write("\n\n\n{:=^100s}".format(""))
fileOutput.write("\n" + str(write_r) + "\n")
fileOutput.write("{:=^100s}\n".format(""))
else:
fileOutput.write("\n")
previous_file = word
# Print info
elif count2 == 1:
fileOutput.write("Line= " + str(data1))
elif count2 == 2:
fileOutput.write(" Column= " + str(data1))
elif count2 == 3:
fileOutput.write(" Check:" + str(data1))
if data1 == " style":
self.style_count += 1
elif data1 == " error":
self.error_count += 1
elif data1 == " note":
self.note_count += 1
else:
self.others_count2 += 1
elif count2 == 4:
fileOutput.write(" Problem:" + str(data1))
else:
fileOutput.write(":" + str(data1))
# Print summary
fileOutput.write("\n\n\n{:=^50s}".format("Summary"))
fileOutput.write("\nTotal problems: " + str(count1 / 3) + "\n")
fileOutput.write("{:=^50s}".format(""))
fileOutput.write("\nProblems in folder:\n")
fileOutput.write(" Core: " + str(self.core_count) + "\n")
fileOutput.write(" Hal: " + str(self.hal_count) + "\n")
fileOutput.write(" Host: " + str(self.host_count) + "\n")
fileOutput.write(" Media: " + str(self.media_count) + "\n")
fileOutput.write(" Others: " + str(self.others_count1) + "\n")
fileOutput.write("{:=^50s}".format(""))
fileOutput.write("\nNumber of different problems:\n")
fileOutput.write(" Style: " + str(self.style_count) + "\n")
fileOutput.write(" Error: " + str(self.error_count) + "\n")
fileOutput.write(" Note: " + str(self.note_count) + "\n")
fileOutput.write(" Others: " + str(self.others_count2) + "\n")
fileOutput.write("{:=^50s}".format("") + "\n")
######################### Main ##########################
def Main():
cpp = CppCheck()
# Open file
fileInput = open(sys.argv[1], "r")
fileOutput = open(sys.argv[2], "w")
cpp.WriteCppcheckFile(fileInput, fileOutput)
return
#################### Main Script Start ###################
Main()
#################### Main Script End #####################
```
<br>
當把寫好的Code丟上GitLab時,GitLab會透過Pipeline來執行任務。依照Stage的順序,執行Stage裡的Job。所以我就是負責寫yml檔,告訴Git要如何執行我們的程式。yml檔中最重要的就是Stage及Job,如果寫錯了程式就不會動,然後script的部份就是程式該如何執行。
```yaml=
#-----------------------------------------------------------------------------
#
# Stage : Cppcheck
# Job : cppcheck
# Rule : N/A
# Tag : $RUNNER_TAG_MASTER
#
#-----------------------------------------------------------------------------
Cppcheck:cppcheck:
stage: Cppcheck
variables:
CPPCHKOUT_FILE : 'cppcheck_out.txt'
OUTPUT_FILE : 'Cppcheck_Output.txt'
script:
- echo "Start cppcheck"
- ls
- git clone http://gitlab-ci-token:${CI_JOB_TOKEN}@10.6.201.211/SSD/$PROJECT_NAME
- cppcheck --enable=$CHECK_TYPE --output-file=$CPPCHKOUT_FILE $PROJECT_NAME
# python cppcheck_trans.py $CPPCHKOUT_FILE [input file] $OUTPUT_FILE [output file]
- python cppcheck_trans.py $CPPCHKOUT_FILE $OUTPUT_FILE
artifacts:
name: "Cppcheck_$PROJECT_NAME$BRANCH_NAME"
paths:
- $OUTPUT_FILE
expire_in: 3 day
tags:
- $RUNNER_TAG_MASTER
```
<br>
## Result
當我們各自的Project都在Local完成Commit後,就可以Push上Global來進行Merge。

當所有Project都推上Server後,就能利用Runner來跑我們的Job了。

最後輸出的檔案如下圖所示:

<br>
## Conclusion
GitLab是一個很方便的開發平台,聽說業界大部分的公司也都是透過GitLab來進行整合開發。不過也許是我們剛開始學習Git,在整合的過程遇到了一些問題:當我們要把Commit完的Branch推上Global時,需要Rebase後才能Run,但在過程中發現一直抓到舊的程式,所以導致我們Run Pipeline一直Failed。
後來我們仔細弄清楚Commit點,並且分清楚彼此的Branch後,再重新建立一條新的Branch,最後終於完成了我們Project的整合。