
W analizatorze PVS-Studio dla języków C i C++ na systemach Linux i macOS, począwszy od wersji 7.04, pojawiła się testowa funkcjonalność umożliwiająca sprawdzenie listy wskazanych plików. Nowy tryb pozwala skonfigurować analizator do sprawdzania commitów i pull requestów. W tym artykule omówimy, jak skonfigurować sprawdzanie listy zmodyfikowanych plików w projekcie GitHub w popularnych systemach CI (Continuous Integration), takich jak Travis CI, Buddy i AppVeyor.
Tryb sprawdzania listy plików
— to narzędzie do wykrywania błędów i potencjalnych luk w kodzie źródłowym programów napisanych w językach C, C++, C# i Java. Działa w systemach 64-bitowych na Windows, Linux i macOS.
W wersji PVS-Studio 7.04 dla Linux i macOS wprowadzono tryb sprawdzania listy plików źródłowych. Działa to dla projektów, których system budowania pozwala na wygenerowanie pliku . Jest on potrzebny, aby analizator mógł wydobyć informacje o kompilacji wskazanych plików. Jeśli Twój system budowania nie obsługuje generacji pliku compile_commands.json, możesz spróbować wygenerować taki plik za pomocą narzędzia .
Ponadto tryb sprawdzania listy plików można wykorzystywać wraz z logiem śledzenia strace uruchomień kompilatora (pvs-studio-analyzer trace). W tym celu musisz najpierw przeprowadzić pełną kompilację projektu i śledzić ją, aby analizator zebrał pełne informacje o parametrach kompilacji wszystkich sprawdzanych plików.
Jednak taki wariant ma istotną wadę — będziesz musiał albo przeprowadzać pełne śledzenie kompilacji całego projektu przy każdym uruchomieniu, co przeczy idei szybkiego sprawdzania commitów. Albo, jeśli zakodujesz sam wynik śledzenia, kolejne uruchomienia analizatora mogą być niekompletne, jeśli po śledzeniu zmieni się struktura zależności plików źródłowych (na przykład, jeśli do jednego z plików źródłowych zostanie dodany nowy #include).
Dlatego nie zalecamy używania trybu sprawdzania listy plików z logiem śledzenia do sprawdzania commitów lub pull requestów. Jeśli możesz wykonywać inkrementalną kompilację podczas sprawdzania commitów, rozważ możliwość użycia trybu .
Lista plików źródłowych do analizy jest zapisywana w pliku tekstowym i przekazywana analizatorowi za pomocą parametru -S:
pvs-studio-analyzer analyze ... -f build/compile_commands.json -S check-list.txtW tym pliku podaje się względne lub absolutne ścieżki do plików, przy czym każdy nowy plik powinien znajdować się w nowej linii. Dopuszcza się podawanie nie tylko nazw plików do analizy, ale także różnego tekstu. Analizator zauważy, że to nie jest plik, i zignoruje ten wiersz. Może to być przydatne do komentowania, gdy pliki są podawane ręcznie. Często jednak lista plików jest generowana podczas analizy w CI, na przykład mogą to być pliki z commitów lub pull requestów.
Teraz dzięki temu trybowi można szybko sprawdzić nowy kod przed jego dodaniem do głównej gałęzi rozwoju. Aby system sprawdzania reagował na obecność ostrzeżeń analizatora, do narzędzia plog-converter dodano flagę —indicate-warnings:
plog-converter ... --indicate-warnings ... -o /path/to/report.tasks ...Z tym flagą konwerter zwróci niezerowy kod, jeśli w raporcie analizatora znajdują się ostrzeżenia. Na podstawie kodu zwrotnego można zablokować hook pre-commit, commit lub pull request, a wygenerowany raport analizatora wyświetlić na ekranie, udostępnić lub wysłać mailem.
Uwaga. Przy pierwszym uruchomieniu analizy listy plików cały projekt zostanie poddany analizie, ponieważ analizator musi wygenerować plik zależności pomiędzy plikami źródłowymi projektu a plikami nagłówkowymi. To cecha analizy plików C i C++. Później plik zależności można zbuforować i zostanie on automatycznie aktualizowany przez analizator. Zaletą sprawdzania commitów podczas używania trybu sprawdzania listy plików w porównaniu do użycia trybu inkrementalnej analizy jest to, że trzeba zbuforować tylko ten plik, a nie pliki obiektowe.
Ogólne zasady analizy pull requestów
Analiza całego projektu zajmuje dość dużo czasu, dlatego warto sprawdzać tylko pewną jego część. Problemem jest to, że trzeba oddzielić nowe pliki od pozostałych plików projektu.
Rozważmy przykład drzewa commitów z dwiema gałęziami:

Wyobraźmy sobie, że commit A1 zawiera dość dużą ilość kodu, który został już sprawdzony. Nieco wcześniej utworzyliśmy gałąź z commita A1 i edytowaliśmy niektóre pliki.
Oczywiście zauważyliście, że po A1 nastąpiły jeszcze dwa commity, ale były to także połączenia innych gałęzi, przecież nie commitujemy do master. I nadeszła pora, kiedy hotfixa jest gotowe. Dlatego pojawił się pull request na połączenie B3 i A3.
Oczywiście, można by sprawdzić cały wynik ich połączenia, ale byłoby to zbyt czasochłonne i nieuzasadnione, ponieważ zmieniono tylko kilka plików. Dlatego efektywniej jest przeanalizować tylko zmienione.
Aby to zrobić, uzyskamy różnicę między gałęziami, będąc w HEAD gałęzi, z której chcemy zmergować do master:
git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list$MERGE_BASE będziemy szczegółowo omawiać później. Chodzi o to, że nie każdy system CI dostarcza niezbędnych informacji o bazie do łączenia, dlatego za każdym razem trzeba wymyślać nowe sposoby uzyskiwania tych danych. Zostanie to szczegółowo opisane poniżej w każdym z opisanych serwisów internetowych.
Tak więc uzyskaliśmy różnicę między gałęziami, a dokładniej — listę nazw plików, które zostały zmienione. Teraz musimy przekazać plik .pvs-pr.list (do którego przekierowaliśmy wyjście powyżej) analizatorowi:
pvs-studio-analyzer analyze -j8
-o PVS-Studio.log
-S .pvs-pr.listPo analizie musimy przekonwertować plik dziennika (PVS-Studio.log) na wygodny format do przyswojenia:
plog-converter -t errorfile PVS-Studio.log --cerr -wTo polecenie wyświetli listę błędów w (standardowy strumień wyjściowy komunikatów o błędach).
Tylko że potrzebujemy nie tylko wyświetlić błędy, ale również poinformować nasz serwis do budowy i testowania o występowaniu problemów. W tym celu do konwertera dodano flagę -W (—indicate-warnings). W przypadku wystąpienia chociaż jednego ostrzeżenia z analizatora, kod wyjścia narzędzia plog-converter zmieni się na 2, co z kolei powiadomi serwis CI o możliwościach błędów w plikach pull request’a.
Travis CI
Konfiguracja została wykonana w postaci pliku .travis.yml. W celu ułatwienia zaleca się umieszczenie wszystkiego w oddzielnym skrypcie bash z funkcjami, które będą wywoływane z pliku .travis.yml (bash nazwa_skryptu.sh nazwa_funkcji).
Będziemy dodawać niezbędny kod do skryptu w bash, w ten sposób uzyskamy większą funkcjonalność. W sekcji install napiszemy następujące:
install:
- bash .travis.sh travis_installJeśli masz jakiekolwiek instrukcje, możesz je przenieść do skryptu, usuwając myślniki.
Otworzymy plik .travis.sh i dodamy instalację analizatora do funkcji 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
}Teraz dodajmy do sekcji script uruchamiania analizy:
script:
- bash .travis.sh travis_scriptI w skrypcie 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
}Ten kod należy uruchomić po budowie projektu, na przykład, jeśli używasz CMake:
travis_script() {
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
cmake $CMAKE_ARGS CMakeLists.txt
make -j8
}Będzie to wyglądać tak:
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
}Pewnie już zwróciłeś uwagę na podane zmienne środowiskowe: $TRAVIS_PULL_REQUEST i $TRAVIS_BRANCH. Travis CI definiuje je automatycznie:
- $TRAVIS_PULL_REQUEST przechowuje numer pull requesta lub false, jeśli to normalna gałąź;
- $TRAVIS_REPO_SLUG przechowuje nazwę repozytorium projektu.
Algorytm pracy tej funkcji:

Travis CI reaguje na kody zakończenia, dlatego obecność ostrzeżeń poinformuje serwis, żeby oznaczył commit jako zawierający błędy.
A teraz przyjrzyjmy się bliżej temu wierszowi kodu:
git diff --name-only origin/HEAD > .pvs-pr.listFaktem jest, że Travis CI automatycznie łączy gałęzie podczas analizy pull requestów:

Dlatego analizujemy A4, a nie B3->A3. Z powodu tej cechy musimy obliczyć różnicę z A3, która jest dokładnie szczytem gałęzi z origin.
Pozostał jedna ważna kwestia — cache'owanie zależności plików nagłówkowych od kompilowanych jednostek translacyjnych (*.c, *.cc, *.cpp itd.). Te zależności analizer oblicza podczas pierwszego uruchomienia w trybie sprawdzania listy plików i następnie zapisuje w katalogu .PVS-Studio. Travis CI pozwala na cache'owanie katalogów, więc zachowamy dane z katalogu .PVS-Studio/:
cache:
directories:
- .PVS-Studio/Ten kod należy dodać do pliku .travis.yml. Ten katalog przechowuje różne dane zebrane po analizie, które znacznie przyspieszają kolejne uruchomienia analizy listy plików lub analizy inkrementalnej. Jeśli tego nie zrobisz, to analizator praktycznie za każdym razem będzie analizować wszystkie pliki.
Buddy
Podobnie jak Travis CI, umożliwia automatyczną kompilację i testowanie projektów przechowywanych na GitHubie. W przeciwieństwie do Travis CI, jest konfigurowany w interfejsie webowym (wsparcie dla bash jest dostępne), więc nie ma potrzeby przechowywania plików konfiguracyjnych w projekcie.
Przede wszystkim musimy dodać nowe działanie do linii kompilacji:

Określimy kompilator, który był używany do kompilacji projektu. Zwróć uwagę na kontener docker, który jest zainstalowany w tym działaniu. Na przykład, dla GCC istnieje specjalny kontener:

Teraz zainstalujemy PVS-Studio i potrzebne narzędzia:

Dodaj do edytora następujące linie:
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-studioTeraz przejdź do zakładki Run (pierwsza ikona) i w odpowiednim polu edytora dodaj następujący kod:
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 -wJeśli czytałeś sekcję poświęconą Travs-CI, to ten kod jest ci już znany, jednak teraz pojawił się nowy etap:

Chodzi o to, że teraz analizujemy nie wynik złączenia, ale HEAD gałęzi, z której powstaje pull request:

Dlatego znajdujemy się w warunkowym commicie B3 i musimy uzyskać różnicę z 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.listAby określić A3 zastosujemy API GitHub:
https://api.github.com/repos/${USERNAME}/${REPO}/pulls/${PULL_REQUEST_ID}Użyliśmy następujących zmiennych, które dostarcza Buddy:
- $BUDDY_EXECUTION_PULL_REQUEST_NO — numer pull request’a;
- $BUDDY_REPO_SLUG — kombinacja nazwy użytkownika i repozytorium (na przykład max/test).
Teraz zapiszemy zmiany, korzystając z przycisku na dole, i włączymy analizę pull request:

W przeciwieństwie do Travis CI, nie musimy podawać .pvs-studio do cache'owania, ponieważ Buddy automatycznie cache'uje wszystkie pliki do kolejnych uruchomień. Pozostało nam tylko to — zapisać login i hasło do PVS-Studio w Buddy. Po zapisaniu zmian wrócimy do Pipeline. Musimy przejść do ustawienia zmiennych i dodać login oraz klucz dla PVS-Studio:

Po tym, pojawienie się nowego pull request'a lub commita uruchomi kontrolę. Jeśli commit zawiera błędy, Buddy wskaże na to na stronie pull request'a.
AppVeyor
Konfiguracja AppVeyor jest podobna do Buddy, ponieważ wszystko odbywa się w interfejsie webowym i nie ma potrzeby dodawania pliku *.yml do repozytorium projektu.
Przejdźmy do zakładki Ustawienia w przeglądzie projektu:

Przewińmy tę stronę w dół i włączmy zapisywanie cache'a dla budowy pull request'ów:

Teraz przejdźmy do zakładki Środowisko, gdzie wskaźmy obraz dla budowy oraz niezbędne zmienne środowiskowe:

Jeśli przeczytałeś poprzednie rozdziały, powinieneś być dobrze zaznajomiony z tymi dwiema zmiennymi — PVS_KEY i PVS_USERNAME. Jeśli nie, przypomnę, że są one niezbędne do weryfikacji licencji analityka PVS-Studio. W przyszłości spotkamy je znowu w skryptach Bash.
Na tej samej stronie na dole wskaźmy folder do cachowania:

Jeśli tego nie zrobimy, będziemy analizować nie tylko kilka plików, ale cały projekt, natomiast wyniki uzyskamy dla wskazanych plików. Dlatego ważne jest, aby wprowadzić poprawną nazwę katalogu.
Teraz nadszedł czas na skrypt do weryfikacji. Otwórzmy zakładkę Testy i wybierzmy Skrypt:

Do tego formularza należy wkleić następujący kod:
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 -wZwróćmy uwagę na następną część kodu:
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
fiPrzypisanie wartości polecenia pwd do zmiennej, która powinna przechowywać tę wartość domyślnie, wydaje się dziwne na pierwszy rzut oka, ale zaraz wszystko wyjaśnię.
Podczas konfigurowania analizatora w AppVeyor napotkałem na niezwykle dziwne zachowanie analizatora. Z jednej strony wszystko działało poprawnie, ale analiza nie była uruchamiana. Spędziłem sporo czasu, aby zauważyć, że znajdujemy się w katalogu /home/appveyor/projects/testcalc/, podczas gdy analizator był przekonany, że jesteśmy w /opt/appveyor/build-agent/. Wtedy zrozumiałem, że zmienna $PWD nieco oszukuje. Dlatego ręcznie zaktualizowałem jej wartość przed rozpoczęciem analizy.
A potem wszystko jak dawniej:

Teraz rozważmy następujący 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"`W nim uzyskujemy różnicę między gałęziami, na których zgłoszono pull request. Potrzebujemy do tego następujących zmiennych środowiskowych:
- $APPVEYOR_PULL_REQUEST_NUMBER — numer pull request;
- $APPVEYOR_REPO_NAME — nazwa użytkownika i repozytorium projektu.
Podsumowanie
Oczywiście nie omówiliśmy wszystkich możliwych usług ciągłej integracji, jednak wszystkie mają bardzo podobną specyfikę działania. Z wyjątkiem buforowania, każda usługa buduje swój własny „rower”, dlatego zawsze jest wszystko inaczej.
Gdzieś, jak w Travis-CI, wystarczą dwie linijki kodu, a buforowanie działa bezbłędnie; gdzie indziej, jak w AppVeyor, wystarczy po prostu wskazać folder w ustawieniach; ale gdzie indziej trzeba stworzyć unikalne klucze i spróbować przekonać system, by dał ci możliwość nadpisania zakumulowanego fragmentu. Dlatego, jeśli chcesz skonfigurować analizę pull requestów na usłudze ciągłej integracji, która nie została omówiona powyżej, upewnij się najpierw, że nie będziesz miał problemów z buforowaniem.
Dziękuję za uwagę. Jeśli coś nie działa, śmiało pisz do nas na . Pomożemy.
Jeśli chcesz podzielić się tym artykułem z anglojęzyczną publicznością, proszę użyj linku do tłumaczenia: Maxim Zvyagintsev. .
Źródło: habr.com
