Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
V analyzátore PVS-Studio pre jazyky C a C++ v systémoch Linux a macOS sa od verzie 7.04 objavila testovacia možnosť na kontrolu zoznamu špecifikovaných súborov. Pomocou nového režimu môžete nakonfigurovať analyzátor tak, aby kontroloval potvrdenia a požiadavky na stiahnutie. Tento článok vám povie, ako nastaviť kontrolu zoznamu zmenených súborov projektu GitHub v takých populárnych systémoch CI (Continuous Integration), ako sú Travis CI, Buddy a AppVeyor.

Režim kontroly zoznamu súborov

Štúdio PVS je nástroj na identifikáciu chýb a potenciálnych zraniteľností v zdrojovom kóde programov napísaných v C, C++, C# a Java. Funguje na 64-bitových systémoch na Windows, Linux a macOS.

Vo verzii PVS-Studio 7.04 pre Linux a macOS sa objavil režim kontroly zoznamu zdrojových súborov. Toto funguje pre projekty, ktorých systém zostavovania vám umožňuje vygenerovať súbor command_commands.json. Je potrebné, aby analyzátor extrahoval informácie o kompilácii špecifikovaných súborov. Ak váš zostavovací systém nepodporuje generovanie súboru compil_commands.json, môžete skúsiť vygenerovať takýto súbor pomocou pomôcky Bear.

Režim kontroly zoznamu súborov možno použiť aj spolu s protokolom sledovania sledovania spúšťania kompilátora (sledovanie pvs-studio-analyzer). Ak to chcete urobiť, musíte najskôr vykonať úplné zostavenie projektu a sledovať ho, aby analyzátor zhromaždil úplné informácie o parametroch kompilácie všetkých kontrolovaných súborov.

Táto možnosť má však významnú nevýhodu – buď budete musieť vykonať úplnú stopu zostavenia celého projektu pri každom spustení, čo je samo o sebe v rozpore s myšlienkou rýchlej kontroly odovzdania. Alebo ak uložíte do vyrovnávacej pamäte samotný výsledok sledovania, následné spustenia analyzátora môžu byť neúplné, ak sa po sledovaní zmení štruktúra závislosti zdrojových súborov (napríklad sa do jedného zo zdrojových súborov pridá nový #include).

Preto neodporúčame používať režim kontroly zoznamu súborov s protokolom sledovania na kontrolu odovzdania alebo žiadostí o stiahnutie. V prípade, že pri kontrole odovzdania môžete vykonať prírastkové zostavenie, zvážte použitie režimu prírastková analýza.

Zoznam zdrojových súborov na analýzu sa uloží do textového súboru a pomocou parametra sa odošle do analyzátora -S:

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

Tento súbor určuje relatívne alebo absolútne cesty k súborom a každý nový súbor musí byť na novom riadku. Je prijateľné špecifikovať nielen názvy súborov na analýzu, ale aj rôzne texty. Analyzátor uvidí, že toto nie je súbor a bude riadok ignorovať. To môže byť užitočné pri komentovaní, ak sú súbory špecifikované manuálne. Často sa však zoznam súborov vygeneruje počas analýzy v CI, napríklad to môžu byť súbory z potvrdenia alebo žiadosti o stiahnutie.

Teraz pomocou tohto režimu môžete rýchlo skontrolovať nový kód skôr, ako sa dostane do hlavnej vývojovej vetvy. Aby sa zabezpečilo, že skenovací systém bude reagovať na varovania analyzátora, pomôcka plog-konvertor pridaná vlajka --indikovať-varovania:

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

S týmto príznakom konvertor vráti nenulový kód, ak sú v správe analyzátora varovania. Pomocou návratového kódu môžete zablokovať požiadavku na hákovanie, potvrdenie alebo stiahnutie a vygenerovanú správu analyzátora je možné zobraziť, zdieľať alebo odoslať e-mailom.

Poznámka. Keď prvýkrát začnete analyzovať zoznam súborov, bude sa analyzovať celý projekt, pretože analyzátor potrebuje vygenerovať súbor závislostí zdrojových súborov projektu na hlavičkových súboroch. Toto je funkcia analýzy súborov C a C++. V budúcnosti môže byť súbor závislostí uložený do vyrovnávacej pamäte a analyzátor ho bude automaticky aktualizovať. Výhodou kontroly odovzdania pri použití režimu kontroly zoznamu súborov oproti režimu prírastkovej analýzy je, že do vyrovnávacej pamäte potrebujete uložiť iba tento súbor a nie súbory objektov.

Všeobecné princípy analýzy požiadaviek na ťahanie

Analýza celého projektu zaberie veľa času, preto má zmysel kontrolovať len určitú jeho časť. Problém je v tom, že musíte oddeliť nové súbory od ostatných súborov projektu.

Pozrime sa na príklad stromu odovzdania s dvoma vetvami:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio

Predstavme si ten záväzok A1 obsahuje pomerne veľké množstvo kódu, ktorý už bol testovaný. O niečo skôr sme z commitu urobili vetvu A1 a zmenil niektoré súbory.

To ste si, samozrejme, všimli až potom A1 došlo k ďalším dvom potvrdeniam, ale boli to aj fúzie iných pobočiek, pretože sa k nim nezaväzujeme majster. A teraz prišiel čas, kedy hotfix pripravený. Preto sa objavila žiadosť o fúziu B3 и A3.

Samozrejme, bolo by možné skontrolovať celý výsledok ich zlúčenia, ale bolo by to časovo náročné a neopodstatnené, keďže sa zmenilo len niekoľko súborov. Preto je efektívnejšie analyzovať len tie zmenené.

Aby sme to dosiahli, dostaneme rozdiel medzi vetvami, ktoré sú v HEAD vetvy, z ktorej sa chceme zlúčiť do hlavnej:

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

$MERGE_BASE podrobne sa na to pozrieme neskôr. Faktom je, že nie každá služba CI poskytuje potrebné informácie o databáze na zlúčenie, takže zakaždým musíte vymyslieť nové spôsoby, ako tieto údaje získať. Toto bude podrobne popísané nižšie v každej z opísaných webových služieb.

Takže sme dostali rozdiel medzi vetvami, alebo skôr zoznam názvov súborov, ktoré boli zmenené. Teraz musíme dať súbor .pvs-pr.list (presmerovali sme naň výstup uvedený vyššie) do analyzátora:

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

Po analýze musíme previesť protokolový súbor (PVS-Studio.log) do ľahko čitateľného formátu:

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

Tento príkaz zobrazí zoznam chýb stderr (štandardný výstup chybového hlásenia).

Len teraz musíme nielen zobraziť chyby, ale aj informovať našu službu pre montáž a testovanie o prítomnosti problémov. Na tento účel bol do prevodníka pridaný príznak -W (--indikovať-varovania). Ak sa vyskytne aspoň jedno varovanie analyzátora, návratový kód pomocného programu plog-konvertor sa zmení na 2, čo zase bude informovať službu CI o prítomnosti potenciálnych chýb v súboroch požiadaviek na stiahnutie.

Travis C.I.

Konfigurácia je vytvorená ako súbor .travis.yml. Pre pohodlie vám odporúčam vložiť všetko do samostatného bash skriptu s funkciami, ktoré sa budú volať zo súboru .travis.yml (bash názov_skriptu.sh názov_funkcie).

Potrebný kód pridáme do skriptu na adrese tresnúť, týmto spôsobom získame viac funkcií. V sekcii inštalovať napíšeme nasledovné:

install:
  - bash .travis.sh travis_install

Ak máte nejaké pokyny, môžete ich preniesť do skriptu a odstrániť pomlčky.

Otvorme súbor .travis.sh a pridajte nastavenie analyzátora do funkcie 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 pridajme do sekcie scenár spustiť analýzu:

script:
  - bash .travis.sh travis_script

A v bash skripte:

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
}

Tento kód je potrebné spustiť po vytvorení projektu, napríklad ak ste mali zostavu na CMake:

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

Dopadne to takto:

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
}

Pravdepodobne ste si už všimli tieto premenné prostredia $TRAVIS_PULL_REQUEST и $TRAVIS_BRANCH. Travis CI ich nezávisle prehlasuje:

  • $TRAVIS_PULL_REQUEST uloží číslo požiadavky na stiahnutie alebo nepravdivý, ak ide o riadnu pobočku;
  • $TRAVIS_REPO_SLUG ukladá názov projektového úložiska.

Algoritmus pre túto funkciu:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Travis CI reaguje na návratové kódy, takže prítomnosť varovaní povie službe, aby označila odovzdanie ako obsahujúce chyby.

Teraz sa pozrime bližšie na tento riadok kódu:

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

Faktom je, že Travis CI automaticky zlučuje pobočky pri analýze požiadavky na stiahnutie:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Preto analyzujeme A4A nie B3->A3. Kvôli tejto funkcii musíme vypočítať rozdiel s A3, čo je práve vrchol vetvy z pôvod.

Zostáva jeden dôležitý detail – ukladanie závislostí hlavičkových súborov na zostavené prekladové jednotky (*.c, *.cc, *.cpp atď.). Analyzátor tieto závislosti vypočíta pri prvom spustení v režime kontroly zoznamu súborov a následne ich uloží do adresára .PVS-Studio. Travis CI vám umožňuje ukladať priečinky do vyrovnávacej pamäte, takže údaje adresára uložíme .PVS-Studio/:

cache:
  directories:
    - .PVS-Studio/

Tento kód je potrebné pridať do súboru .travis.yml. Tento adresár ukladá rôzne dáta zozbierané po analýze, čo výrazne urýchli následné spustenie analýzy zoznamu súborov alebo prírastkovej analýzy. Ak to neurobíte, analyzátor skutočne zakaždým analyzuje všetky súbory.

kámoš

Rovnako ako Travis CI, kámoš poskytuje možnosť automaticky vytvárať a testovať projekty uložené na GitHub. Na rozdiel od Travis CI sa konfiguruje vo webovom rozhraní (k dispozícii je podpora bash), takže nie je potrebné ukladať konfiguračné súbory do projektu.

Najprv musíme na montážnu linku pridať novú akciu:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Označme kompilátor, ktorý bol použitý na zostavenie projektu. Všimnite si kontajner ukotvenia, ktorý je nainštalovaný v tejto akcii. Napríklad existuje špeciálny kontajner pre GCC:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Teraz nainštalujeme PVS-Studio a potrebné nástroje:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Pridajme do editora nasledujúce riadky:

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

Teraz prejdeme na kartu Spustiť (prvá ikona) a do príslušného poľa editora pridáme nasledujúci kód:

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

Ak si prečítate časť o Travs-CI, tento kód je vám už známy, teraz je tu však nová etapa:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Faktom je, že teraz neanalyzujeme výsledok zlúčenia, ale HEAD vetvy, z ktorej sa odoslala požiadavka na stiahnutie:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Takže sme v podmienečnom odovzdaní B3 a my potrebujeme dostať rozdiel 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.list

Určiť A3 Použime GitHub API:

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

Použili sme nasledujúce premenné, ktoré poskytuje Buddy:

  • $BUDDY_EXECUTION_PULL_REQEUST_NO — vytiahnuť číslo požiadavky;
  • $BUDDY_REPO_SLUG — kombinácia používateľského mena a úložiska (napríklad max/test).

Teraz uložíme zmeny pomocou tlačidla nižšie a povolíme analýzu žiadosti o stiahnutie:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Na rozdiel od Travisa CI nemusíme špecifikovať .pvs-studio pre ukladanie do vyrovnávacej pamäte, pretože Buddy automaticky ukladá všetky súbory do vyrovnávacej pamäte pre následné spustenie. Posledná vec, ktorú teda zostáva, je uložiť prihlasovacie meno a heslo do PVS-Studio v Buddy. Po uložení zmien sa vrátime späť do Pipeline. Musíme prejsť na nastavenie premenných a pridanie prihlasovacieho mena a kľúča pre PVS-Studio:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Potom objavenie sa novej žiadosti o stiahnutie alebo odovzdania spustí kontrolu. Ak potvrdenie obsahuje chyby, Buddy to uvedie na stránke žiadosti o stiahnutie.

AppVeyor

Nastavenie AppVeyor je podobné Buddymu, keďže všetko prebieha vo webovom rozhraní a nie je potrebné pridávať *.yml súbor do projektového úložiska.

Poďme na kartu Nastavenia v prehľade projektu:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Prejdite na túto stránku a povoľte ukladanie do vyrovnávacej pamäte na zhromažďovanie žiadostí o stiahnutie:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Teraz prejdeme na kartu Prostredie, kde zadáme obrázok na zostavenie a potrebné premenné prostredia:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Ak ste čítali predchádzajúce časti, dobre poznáte tieto dve premenné − PVS_KEY и PVS_USERNAME. Ak nie, pripomeniem, že sú potrebné na overenie licencie analyzátora PVS-Studio. V budúcnosti ich opäť uvidíme v Bash skriptoch.

Na tej istej stránke nižšie uvádzame priečinok na ukladanie do vyrovnávacej pamäte:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Ak to neurobíme, namiesto dvojice súborov budeme analyzovať celý projekt, ale výstup získame zo zadaných súborov. Preto je dôležité zadať správny názov adresára.

Teraz je čas na testovanie skriptu. Otvorte kartu Testy a vyberte Skript:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Do tohto formulára musíte vložiť nasledujúci kód:

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

Venujme pozornosť nasledujúcej časti kódu:

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

Pomerne špecifické priradenie hodnoty príkazu pwd premennej, ktorá by mala túto predvolenú hodnotu uchovávať, pôsobí na prvý pohľad zvláštne, všetko však teraz vysvetlím.

Pri nastavovaní analyzátora v AppVeyor som sa stretol s mimoriadne zvláštnym správaním analyzátora. Na jednej strane všetko fungovalo správne, ale analýza sa nezačala. Strávil som veľa času tým, že som si všimol, že sme v adresári /home/appveyor/projects/testcalc/ a analyzátor si je istý, že sme v /opt/appveyor/build-agent/. Potom som si uvedomil, že premenná $ PWD trochu klamala. Z tohto dôvodu som pred spustením analýzy manuálne aktualizoval jeho hodnotu.

A potom je všetko ako predtým:

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio
Teraz zvážte nasledujúci 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"`

V ňom dostaneme rozdiel medzi vetvami, nad ktorými je deklarovaná požiadavka na stiahnutie. Na to potrebujeme nasledujúce premenné prostredia:

  • $APPVEYOR_PULL_REQUEST_NUMBER — stiahnuť číslo požiadavky;
  • $APPVEYOR_REPO_NAME - meno používateľa a úložisko projektu.

Záver

Samozrejme, nezvážili sme všetky možné služby nepretržitej integrácie, ale všetky majú veľmi podobné prevádzkové špecifiká. S výnimkou ukladania do vyrovnávacej pamäte si každá služba vytvára svoj vlastný „bicykel“, takže vždy je všetko inak.

Niekde, ako v Travis-CI, pár riadkov kódu a ukladanie do vyrovnávacej pamäte funguje bezchybne; niekde, napríklad v AppVeyor, stačí zadať priečinok v nastaveniach; ale niekde musíte vytvoriť jedinečné kľúče a pokúsiť sa presvedčiť systém, aby vám dal možnosť prepísať fragment uložený vo vyrovnávacej pamäti. Preto, ak chcete nastaviť analýzu požiadaviek na stiahnutie v službe nepretržitej integrácie, o ktorej sa nehovorilo vyššie, najprv sa uistite, že nebudete mať problémy s vyrovnávacou pamäťou.

Ďakujem za tvoju pozornosť. Ak niečo nefunguje, pokojne nám napíšte na podpora. Poradíme a pomôžeme.

Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio

Ak chcete tento článok zdieľať s anglicky hovoriacim publikom, použite odkaz na preklad: Maxim Zvjagincev. Analýza potvrdení a požiadaviek na stiahnutie v Travis CI, Buddy a AppVeyor pomocou PVS-Studio.

Zdroj: hab.com

Pridať komentár