Analiza commit-urilor și a pull request-urilor în Travis CI, Buddy și AppVeyor cu ajutorul PVS-Studio.

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
În analiza PVS-Studio pentru limbile C și C++ pe Linux și macOS, începând cu versiunea 7.04, a fost introdusă o funcționalitate experimentală care permite verificarea listei de fișiere specificate. Prin noul mod, analiza poate fi configurată pentru a verifica commit-urile și pull request-urile. În acest articol, vom discuta despre cum să configurăm verificarea listei de fișiere modificate pentru proiectele de pe GitHub în sisteme CI (Integrare Continuă) populare, cum ar fi Travis CI, Buddy și AppVeyor.

Modul de verificare a listei de fișiere

PVS-Studio — este un instrument pentru identificarea erorilor și vulnerabilităților potențiale din codul sursă al programelor scrise în limbile C, C++, C# și Java. Funcționează pe sisteme de 64 de biți pe Windows, Linux și macOS.

În versiunea PVS-Studio 7.04 pentru Linux și macOS a fost introdus modul de verificare a listei de fișiere sursă. Acesta funcționează pentru proiecte ale căror sistem de construire poate genera un fișier compile_commands.json. Acesta este necesar pentru ca analizatorul să extragă informații despre compilarea fișierelor specificate. Dacă sistemul tău de construire nu permite generarea fișierului compile_commands.json, poți încerca să generezi un astfel de fișier folosind utilitarul Bear.

De asemenea, modul de verificare a listei de fișiere poate fi utilizat împreună cu jurnalul de urmărire strace al lansărilor compilatoarelor (pvs-studio-analyzer trace). Pentru aceasta, va trebui mai întâi să efectuezi o construcție completă a proiectului și să o urmărești, astfel încât analizatorul să colecteze informații complete despre parametrii de compilare pentru toate fișierele verificate.

Cu toate acestea, această opțiune are un dezavantaj semnificativ — va fi nevoie fie să efectuezi o urmărire completă a construcției întregului proiect la fiecare lansare, ceea ce contrazice ideea de verificare rapidă a commit-ului. Fie, dacă reții rezultatul urmării, lansările ulterioare ale analizatorului pot fi incomplete, dacă după urmărire se schimbă structura dependențelor fișierelor sursă (de exemplu, se adaugă un nou #include într-unul din fișierele sursă).

Prin urmare, nu recomandăm utilizarea modului de verificare a listei de fișiere cu jurnalul de urmărire pentru verificarea commit-urilor sau a pull request-urilor. Dacă poți efectua o construcție incrementală în timpul verificării commit-ului, ia în considerare utilizarea modului analizei incrementale.

Lista fișierelor sursă de analizat este salvată într-un fișier text și transmisă analizatorului prin parametrul -S:

pvs-studio-analyzer analyze ... -f build/compile_commands.json -S check-list.txt

În acest fișier se specifică căile relative sau absolute către fișiere, fiecare fișier nou trebuind să fie pe o linie nouă. Este permis să se specifice nu doar numele fișierelor pentru analiză, ci și diferite texte. Analizatorul va observa că acesta nu este un fișier și va ignora linia. Acest lucru poate fi util pentru comentarii, în cazul în care fișierele sunt specificate manual. Totuși, de multe ori lista fișierelor va fi generată în timpul analizei în CI, de exemplu, acestea pot fi fișiere dintr-un commit sau pull request.

Acum, cu ajutorul acestui mod, este posibil să verificați rapid noul cod înainte de a ajunge în ramura principală de dezvoltare. Pentru ca sistemul de verificare să reacționeze la avertizările analizatorului, în utilitarul plog-converter a fost adăugat un flag —indicate-warnings:

plog-converter ... --indicate-warnings ... -o /path/to/report.tasks ...

Cu acest flag, convertorul va returna un cod diferit de zero dacă în raportul analizatorului există avertizări. Pe baza codului de returnare, se poate bloca hook-ul de precommit, commit-ul sau pull request-ul, iar raportul generat al analizatorului poate fi afișat pe ecran, partajat sau trimis prin email.

Notă. La prima rulare, analiza listei de fișiere va analiza întregul proiect, deoarece analizatorul trebuie să genereze fișierul de dependențe al fișierelor sursă ale proiectului față de fișierele header. Aceasta este o caracteristică a analizei fișierelor C și C++. Ulterior, fișierul de dependențe poate fi cached și va fi actualizat automat de analizator. Avantajul verificării commit-urilor în utilizarea modului de verificare a listei de fișiere față de utilizarea modului de analiză incrementală este că trebuie să se cacheze doar acest fișier, nu și fișierele obiect.

Principiile generale ale analizei pull request-ului

Analiza întregului proiect durează destul de mult timp, astfel că are sens să se verifice doar o anumită parte a acestuia. Problema este că trebuie să se separe fișierele noi de celelalte fișiere ale proiectului.

Să luăm un exemplu de arbore de commit-uri cu două ramuri:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio

Să presupunem că commit-ul A1 conține o cantitate destul de mare de cod care a fost deja verificat. Cu puțin timp înainte, am făcut o ramură din commit-ul A1 și am modificat anumite fișiere.

Desigur, ați observat că după A1 au avut loc încă două commit-uri, dar acestea au fost, de asemenea, fuziuni ale altor ramuri, deoarece nu ne angajăm în master. Și acum a venit vremea când hotfix este gata. De aceea a apărut un pull request pentru fuziune B3 și A3.

Desigur, ar fi fost posibil să verificăm întregul rezultat al fuziunii lor, dar ar fi fost prea lung și nejustificat, deoarece doar câteva fișiere au fost modificate. Prin urmare, este mai eficient să analizăm doar fișierele modificate.

Pentru aceasta, vom obține diferența dintre ramuri, aflându-ne în ramura HEAD din care dorim să fuzionăm în master:

git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list

$MERGE_BASE vom analiza acest aspect în detaliu mai târziu. Problema este că nu toate serviciile CI oferă informațiile necesare despre baza pentru fuziune, astfel că, de fiecare dată, este nevoie să găsim noi metode de a obține aceste date. Acest lucru va fi descris în detaliu mai jos pentru fiecare dintre serviciile web descrise.

Așadar, am obținut diferența dintre ramuri, mai precis — lista numelui fișierelor care au fost modificate. Acum trebuie să oferim fișierul .pvs-pr.list (în care am redirecționat ieșirea mai sus) analizorului:

pvs-studio-analyzer analyze -j8 
                            -o PVS-Studio.log 
                            -S .pvs-pr.list

După analiză, trebuie să convertim fișierul de loguri (PVS-Studio.log) într-un format mai ușor de înțeles:

plog-converter -t errorfile PVS-Studio.log --cerr -w

Această comandă va genera o listă de erori în stderr (fluxul standard de ieșire al mesajelor de eroare).

Totuși, trebuie să nu doar afișăm erorile, ci și să informăm serviciul nostru pentru compilare și testare despre existența problemelor. Pentru aceasta, în converter a fost adăugat un flag -W (—indicate-warnings). În cazul în care există cel puțin un avertisment al analizorului, codul de returnare al utilitarului plog-converter se va schimba la 2, ceea ce, la rândul său, va informa serviciul CI despre existența posibilelor erori în fișierele pull request-ului.

Travis CI

Configurația este realizată sub formă de fișier .travis.yml. Pentru comoditate, recomand să mutați totul într-un script bash separat cu funcții care vor fi apelate din fișierul .travis.yml (bash nume_script.sh nume_funcție).

Vom adăuga codul necesar în script în bash, astfel obținem o funcționalitate mai mare. În secțiunea install vom scrie următoarele:

install:
  - bash .travis.sh travis_install

Dacă ați avut vreo instrucțiune, o puteți muta în script, eliminând liniile cu semne de minus.

Vom deschide fișierul .travis.sh și vom adăuga instalarea analizorului în funcția travis_install():

travis_install() {
  wget -q -O - https://files.viva64.com/etc/pubkey.txt 
    | sudo apt-key add -
  sudo wget -O /etc/apt/sources.list.d/viva64.list 
    https://files.viva64.com/etc/viva64.list
  
  sudo apt-get update -qq
  sudo apt-get install -qq pvs-studio 
}

Acum să adăugăm în secțiunea script execuția analizei:

script:
  - bash .travis.sh travis_script

Și în scriptul bash:

travis_script() {
  pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY
  
  if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    git diff --name-only origin/HEAD > .pvs-pr.list
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                -S .pvs-pr.list 
                                --disableLicenseExpirationCheck
  else
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                --disableLicenseExpirationCheck
  fi
  
  plog-converter -t errorfile PVS-Studio.log --cerr -w
}

Acest cod trebuie rulat după compilarea proiectului, de exemplu, dacă ați avut o compilare cu CMake:

travis_script() {
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
  cmake $CMAKE_ARGS CMakeLists.txt
  make -j8
}

Va trebui să arate astfel:

travis_script() {
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
  cmake $CMAKE_ARGS CMakeLists.txt
  make -j8
  
  pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY
  
  if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    git diff --name-only origin/HEAD > .pvs-pr.list
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                -S .pvs-pr.list 
                                --disableLicenseExpirationCheck
  else
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                --disableLicenseExpirationCheck
  fi
  
  plog-converter -t errorfile PVS-Studio.log --cerr -w
}

Probabil ați observat deja variabilele de mediu menționate: $TRAVIS_PULL_REQUEST și $TRAVIS_BRANCH. Travis CI le declară de la sine:

  • $TRAVIS_PULL_REQUEST păstrează numărul cererii de extragere sau false, dacă este o ramură obișnuită;
  • $TRAVIS_REPO_SLUG păstrează numele depozitului proiectului.

Algoritmul de funcționare al acestei funcții:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Travis CI reacționează la codurile de returnare, astfel încât prezența avertismentelor îi va indica serviciului să marcheze comiterea ca având erori.

Acum să examinăm mai în detaliu această linie de cod:

git diff --name-only origin/HEAD > .pvs-pr.list

Problema este că Travis CI realizează automat fuziunea ramurilor în timpul analizei cererii de extragere:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
De aceea analizăm A4, nu B3->A3. Din această caracteristică trebuie să calculăm diferența cu A3, care este vârful ramurii din origin.

A mai rămas un detaliu important - caching-ul dependențelor fișierelor header din unitățile de compilare (*.c, *.cc, *.cpp etc.). Aceste dependențe sunt calculate de analyzer la prima rulare în modul de verificare a listei de fișiere și sunt apoi salvate în directorul .PVS-Studio. Travis CI permite caching-ul folderelor, așa că vom salva datele directorului .PVS-Studio/:

cache:
  directories:
    - .PVS-Studio/

Acest cod trebuie adăugat în fișier .travis.yml. Această directoare conține diverse date, colectate în urma analizei, care accelerează semnificativ următoarele lansări ale analizei listei de fișiere sau analiza incrementală. Dacă acest lucru nu se face, analistul va analiza, de fapt, toate fișierele de fiecare dată.

Buddy

La fel ca Travis CI, Buddy oferă posibilitatea de a construi și testa automat proiecte care sunt stocate pe GitHub. Spre deosebire de Travis CI, acesta se configurează în interfața web (există suport pentru bash), așa că nu este necesar să păstrați fișierele de configurare în proiect.

În primul rând, trebuie să adăugăm o nouă acțiune în linia de construire:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Vom specifica compilatorul care a fost folosit pentru a construi proiectul. Rețineți containerul docker care este instalat în această acțiune. De exemplu, pentru GCC există un container special:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Acum vom instala PVS-Studio și utilitarele necesare:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Adăugați următoarele linii în editor:

apt-get update && apt-get -y install wget gnupg jq

wget -q -O - https://files.viva64.com/etc/pubkey.txt | apt-key add -
wget -O /etc/apt/sources.list.d/viva64.list 
  https://files.viva64.com/etc/viva64.list

apt-get update && apt-get -y install pvs-studio

Acum trecem la tab-ul Run (prima pictogramă) și în câmpul corespunzător editorului adăugăm următorul cod:

pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY

if [ "$BUDDY_EXECUTION_PULL_REQUEST_NO" != '' ]; then
  PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

plog-converter -t errorfile PVS-Studio.log --cerr -w

Dacă ați citit secțiunea dedicată Travs-CI, acest cod vă este deja familiar, însă acum a apărut o nouă etapă:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Ideea este că acum analizăm nu rezultatul fuziunii, ci HEAD-ul ramurii din care se face pull request:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Prin urmare, ne aflăm într-un commit condiționat B3 și trebuie să obținem diferența cu A3:

PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`
git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list

Pentru identificarea A3 vom folosi API-ul GitHub:

https://api.github.com/repos/${USERNAME}/${REPO}/pulls/${PULL_REQUEST_ID}

Am folosit următoarele variabile pe care le oferă Buddy:

  • $BUDDY_EXECUTION_PULL_REQEUST_NO — numărul pull request-ului;
  • $BUDDY_REPO_SLUG — combinația dintre numele utilizatorului și repository (de exemplu, max/test).

Acum vom salva modificările utilizând butonul de mai jos și vom activa analiza pull request-ului:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Spre deosebire de Travis CI, nu este necesar să indicăm .pvs-studio pentru cache, deoarece Buddy cache-uiește automat toate fișierele pentru execuțiile viitoare. Așadar, rămâne ultimul pas – să salvăm utilizatorul și parola pentru PVS-Studio în Buddy. După salvarea modificărilor, ne vom întoarce în Pipeline. Trebuie să mergem la configurarea variabilelor și să adăugăm utilizatorul și cheia pentru PVS-Studio:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
După aceasta, apariția unui nou pull request sau commit va declanșa verificarea. Dacă commitul conține erori, Buddy va indica acest lucru pe pagina pull request-ului.

AppVeyor

Configurarea AppVeyor este similară cu cea de la Buddy, deoarece totul se desfășoară în interfața web și nu este necesar să adăugăm un fișier *.yml în repository-ul proiectului.

Să mergem la fila Settings în vizualizarea proiectului:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Derulăm această pagină în jos și activăm salvarea cache-ului pentru build-urile pull request-urilor:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Acum mergem la fila Environment, unde vom specifica imaginea pentru build și variabilele de mediu necesare:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Dacă ați citit secțiunile anterioare, sunteți familiarizat cu aceste două variabile – PVS_KEY și PVS_USERNAME. Dacă nu, îmi permit să reamintesc că acestea sunt necesare pentru verificarea licenței analizorului PVS-Studio. În continuare, ne vom întâlni din nou cu ele în scripturile Bash.

Pe aceeași pagină, în partea de jos, vom specifica folderul pentru cache:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Dacă nu facem acest lucru, vom analiza întreaga stațiune în loc de câteva fișiere, dar rezultatul va fi generat doar pentru fișierele specificate. Așadar, este important să introducem numele corect al directorului.

Acum a venit vremea scriptului pentru verificare. Să deschidem fila Tests și să selectăm Script:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
În acest formular trebuie să inserați următorul cod:

sudo apt-get update && sudo apt-get -y install jq

wget -q -O - https://files.viva64.com/etc/pubkey.txt 
  | sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list 
  https://files.viva64.com/etc/viva64.list

sudo apt-get update && sudo apt-get -y install pvs-studio

pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY

PWD=$(pwd -L)
if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then
  PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              --dump-files --dump-log pvs-dump.log 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

plog-converter -t errorfile PVS-Studio.log --cerr -w

Să ne concentrăm asupra următoarei părți a codului:

PWD=$(pwd -L)
if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then
  PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
  MERGE_BASE=`wget -qO - 
   https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
   | jq -r ".base.ref"``

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              --dump-files --dump-log pvs-dump.log 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

Deși la prima vedere actualmente, atribuirea unui parametru pentru comanda pwd unei variabile ar părea ciudată, permiteți-mi să explic.

În timpul configurării analistului în AppVeyor, am întâmpinat un comportament extrem de ciudat al acestuia. Pe de o parte, totul funcționa corect, dar analiza nu era inițiată. Am petrecut mult timp realizând că ne aflăm în directorul /home/appveyor/projects/testcalc/, în timp ce analistul era sigur că ne aflăm în /opt/appveyor/build-agent/. Atunci am realizat că variabila $PWD minte puțin. Din acest motiv, am actualizat manual valoarea acesteia înainte de a porni analiza.

Și apoi, totul este ca înainte:

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio
Acum să analizăm următorul fragment:

PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
MERGE_BASE=`wget -qO - 
  https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
  | jq -r ".base.ref"`

Aici obținem diferența între ramurile pentru care a fost declarat pull request. Pentru aceasta, avem nevoie de următoarele variabile de mediu:

  • $APPVEYOR_PULL_REQUEST_NUMBER — numărul pull request-ului;
  • $APPVEYOR_REPO_NAME — numele utilizatorului și depozitul proiectului.

Concluzie

Desigur, nu am acoperit toate serviciile posibile de integrare continuă, dar toate au o specificitate extrem de similară. Cu excepția caching-ului, fiecare serviciu creează propriul „bicicletă”, astfel că, întotdeauna, lucrurile funcționează diferit.

Undeva, ca în Travis-CI, câteva linii de cod și caching-ul funcționează perfect; altundeva, ca în AppVeyor, trebuie doar să specifici folderul în setări; dar în alte cazuri, trebuie să creezi chei unice și să încerci să convingi sistemul să-ți permită să rescrii fragmentul cache-uit. Așadar, dacă doriți să configurați analiza pull request-urilor pe un serviciu de integrare continuă care nu a fost analizat mai sus, asigurați-vă mai întâi că nu veți întâmpina probleme cu caching-ul.

Vă mulțumim pentru atenție. Dacă ceva nu funcționează, nu ezitați să ne scrieți la asistență. Vă vom ajuta și vă vom oferi suport.

Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor cu PVS-Studio

Dacă doriți să împărtășiți acest articol cu o audiență vorbitoare de engleză, vă rog să folosiți linkul către traducere: Maxim Zvyagintsev. Analiza comitelor și a cererilor de extragere în Travis CI, Buddy și AppVeyor utilizând PVS-Studio.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster