Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Ing penganalisa PVS-Studio kanggo basa C lan C ++ ing Linux lan macOS, wiwit saka versi 7.04, pilihan tes wis katon kanggo mriksa dhaptar file sing ditemtokake. Nggunakake mode anyar, sampeyan bisa ngatur analyzer kanggo mriksa commits lan narik panjalukan. Artikel iki bakal ngandhani carane nyiyapake mriksa dhaptar file sing diganti saka proyek GitHub ing sistem CI (Continuous Integration) sing populer kaya Travis CI, Buddy lan AppVeyor.

Mode mriksa dhaptar file

PVS Studio minangka alat kanggo ngenali kesalahan lan kerentanan potensial ing kode sumber program sing ditulis ing C, C ++, C # lan Jawa. Bisa digunakake ing sistem 64-bit ing Windows, Linux lan macOS.

Ing versi PVS-Studio 7.04 kanggo Linux lan macOS, mode kanggo mriksa dhaptar file sumber wis katon. Iki bisa digunakake kanggo proyek sing sistem mbangun ngidini sampeyan ngasilake file compile_commands.json. Analisa dibutuhake kanggo ngekstrak informasi babagan kompilasi file sing ditemtokake. Yen sistem mbangun sampeyan ora ndhukung ngasilake file compile_commands.json, sampeyan bisa nyoba nggawe file kasebut nggunakake sarana bear.

Uga, mode mriksa dhaptar file bisa digunakake bebarengan karo strace log saka compiler diluncurake (pvs-studio-analyzer trace). Kanggo nindakake iki, sampeyan kudu nggawe proyek lengkap lan nglacak supaya analisa ngumpulake informasi lengkap babagan paramèter kompilasi kabeh file sing dicenthang.

Nanging, pilihan iki nduweni kekurangan sing signifikan - sampeyan kudu nindakake tilak lengkap kabeh proyek saben-saben sampeyan mbukak, sing dhewe mbantah ide mriksa komit kanthi cepet. Utawa, yen sampeyan nyimpen asil trace dhewe, analisa sabanjure bisa uga ora lengkap yen struktur ketergantungan file sumber diganti sawise trace (contone, #include anyar ditambahake menyang salah sawijining file sumber).

Mulane, kita ora nyaranake nggunakake mode mriksa dhaptar file kanthi log tilak kanggo mriksa commits utawa narik panjalukan. Yen sampeyan bisa nggawe tambahan nalika mriksa komit, coba gunakake mode kasebut analisis tambahan.

Dhaptar file sumber kanggo analisis disimpen ing file teks lan diterusake menyang penganalisis nggunakake parameter kasebut -S:

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

Berkas iki nemtokake path relatif utawa absolut menyang file, lan saben file anyar kudu ing baris anyar. Ditampa ora mung jeneng file kanggo analisis, nanging uga macem-macem teks. Analisa bakal weruh manawa iki dudu file lan bakal nglirwakake baris kasebut. Iki bisa migunani kanggo menehi komentar yen file kasebut kanthi manual. Nanging, asring dhaptar file bakal digawe sajrone analisis ing CI, umpamane, iki bisa uga file saka panjaluk komitmen utawa narik.

Saiki, nggunakake mode iki, sampeyan bisa mriksa kode anyar kanthi cepet sadurunge mlebu ing cabang pangembangan utama. Kanggo mesthekake yen sistem mindhai nanggapi bebaya analyzer, sarana plug-converter flag ditambahake --nuduhake-warning:

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

Kanthi gendera iki, konverter bakal ngasilake kode non-nol yen ana bebaya ing laporan analisa. Nggunakake kode bali, sampeyan bisa mblokir panjalukan precommit, commit, utawa narik, lan laporan analisa sing digawe bisa ditampilake, dituduhake, utawa dikirim liwat email.

Cathetan. Nalika sampeyan miwiti nganalisa dhaptar file, kabeh proyek bakal dianalisis, amarga analyzer perlu kanggo generate file dependensi file sumber project ing file header. Iki minangka fitur kanggo nganalisa file C lan C++. Ing mangsa ngarep, file dependensi bisa di-cache lan bakal dianyari kanthi otomatis dening penganalisis. Kauntungan saka mriksa commit nalika nggunakake mode mriksa dhaptar file tinimbang nggunakake mode analisis tambahan yaiku sampeyan mung kudu nyimpen file kasebut lan dudu file obyek.

Prinsip umum analisis panjalukan tarik

Nganalisa kabeh proyek mbutuhake wektu akeh, saengga bisa dipriksa mung bagean tartamtu. Masalahe yaiku sampeyan kudu misahake file anyar saka file proyek liyane.

Ayo goleki conto wit komit kanthi rong cabang:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio

Ayo mbayangno komitmen kasebut A1 ngemot jumlah kode sing cukup akeh sing wis diuji. A little sadurungé kita nggawe cabang saka commit A1 lan ngganti sawetara file.

Sampeyan, mesthi, weruh sing sawise A1 ana rong komitmen liyane, nanging iki uga minangka gabungan saka cabang liyane, amarga kita ora setya Master. Lan saiki wis teka nalika hotfix siap. Pramila panyuwunan tarik kanggo penggabungan muncul B3 и A3.

Mesti wae, iku bakal bisa kanggo mriksa kabeh asil gabungan sing, nanging iki bakal banget wektu-akeh lan ora adil, amarga mung sawetara file diganti. Mulane, luwih efisien kanggo nganalisa mung sing diganti.

Kanggo nindakake iki, kita entuk prabédan antarane cabang, sing ana ing KEPALA cabang sing arep digabung dadi master:

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

$MERGE_BASE kita bakal katon ing rinci mengko. Kasunyatane ora saben layanan CI nyedhiyakake informasi sing dibutuhake babagan database kanggo gabung, dadi saben sampeyan kudu nggawe cara anyar kanggo entuk data kasebut. Iki bakal diterangake kanthi rinci ing ngisor iki ing saben layanan web sing diterangake.

Dadi, kita entuk bedane antarane cabang, utawa luwih, dhaptar jeneng file sing diganti. Saiki kita kudu menehi file .pvs-pr.list (kita ngarahake output ing ndhuwur) menyang analisa:

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

Sawise analisis, kita kudu ngowahi file log (PVS-Studio.log) dadi format sing gampang diwaca:

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

Printah iki bakal dhaptar kesalahan ing stderr (output pesen kesalahan standar).

Mung saiki kita kudu ora mung nampilake kesalahan, nanging uga ngandhani layanan kita kanggo perakitan lan nguji babagan anané masalah. Kanggo maksud iki, gendéra ditambahake menyang konverter -W (--nuduhake-warning). Yen ana paling ora siji bebaya analyzer, kode bali sarana plug-converter bakal ngganti kanggo 2, kang siji bakal ngandhani layanan CI bab ngarsane kasalahan potensial ing file request narik.

Travis C.I.

Konfigurasi digawe minangka file .travis.yml. Kanggo penak, aku menehi saran supaya sampeyan sijine kabeh menyang script bash kapisah karo fungsi sing bakal disebut saka file .travis.yml (bash script_name.sh function_name).

Kita bakal nambah kode perlu kanggo script ing bash, kanthi cara iki kita bakal entuk luwih akeh fungsi. Ing bagean masang ayo nulis ing ngisor iki:

install:
  - bash .travis.sh travis_install

Yen sampeyan duwe instruksi, sampeyan bisa nransfer menyang script, mbusak hyphens.

Ayo mbukak file kasebut .travis.sh lan nambah setelan analyzer kanggo fungsi 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 
}

Saiki ayo ditambahake menyang bagean kasebut script analisis run:

script:
  - bash .travis.sh travis_script

Lan ing skrip 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
}

Kode iki kudu ditindakake sawise mbangun proyek, contone, yen sampeyan duwe mbangun CMake:

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

Bakal dadi kaya iki:

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
}

Sampeyan mbokmenawa wis ngerteni variabel lingkungan kasebut $TRAVIS_PULL_REQUEST и $TRAVIS_BRANCH. Travis CI nyatakake kanthi bebas:

  • $TRAVIS_PULL_REQUEST nyimpen nomer request narik utawa palsu, yen iki cabang biasa;
  • $TRAVIS_REPO_SLUG nyimpen jeneng repositori project.

Algoritma kanggo fungsi iki:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Travis CI nanggapi kode bali, supaya ana bebaya bakal ngandhani layanan kasebut kanggo menehi tandha komitmen minangka kesalahan.

Saiki ayo deleng baris kode iki:

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

Kasunyatane yaiku Travis CI kanthi otomatis nggabungake cabang nalika nganalisa panjaluk tarik:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Mulane kita nganalisa A4, lan ora B3->A3. Amarga fitur iki, kita kudu ngetung prabédan karo A3, kang sabenere ndhuwur cabang saka asal.

Ana siji rincian penting sing isih ana - caching dependensi file header ing unit terjemahan sing dikompilasi (*.c, *.cc, *.cpp, lsp.). Analyzer ngetung dependensi iki nalika pisanan dibukak ing mode mriksa dhaftar file lan banjur disimpen ing direktori .PVS-Studio. Travis CI ngijini sampeyan kanggo cache folder, supaya kita bakal nyimpen data direktori .PVS-Studio/:

cache:
  directories:
    - .PVS-Studio/

Kode iki kudu ditambahake menyang file .travis.yml. Direktori iki nyimpen macem-macem data sing diklumpukake sawise analisis, sing bakal nyepetake analisis dhaptar file utawa analisis tambahan. Yen iki ora rampung, analisa bakal bener-bener nganalisa kabeh file saben wektu.

Buddy

Kaya Travis CI, Buddy nyedhiyakake kemampuan kanggo mbangun lan nguji proyek kanthi otomatis sing disimpen ing GitHub. Ora kaya Travis CI, dikonfigurasi ing antarmuka web (ndhukung bash kasedhiya), mula ora perlu nyimpen file konfigurasi ing proyek kasebut.

Kaping pisanan, kita kudu nambah tumindak anyar menyang baris perakitan:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Ayo nuduhake kompiler sing digunakake kanggo mbangun proyek kasebut. Wigati wadhah docker sing diinstal ing tumindak iki. Contone, ana wadhah khusus kanggo GCC:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Saiki ayo instal PVS-Studio lan utilitas sing dibutuhake:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Ayo ditambahake baris ing ngisor iki menyang 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

Saiki ayo pindhah menyang tab Run (lambang pisanan) lan tambahake kode ing ngisor iki menyang kolom editor sing cocog:

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

Yen sampeyan maca bagean ing Travs-CI, kode iki wis dikenal, nanging saiki ana tahap anyar:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Kasunyatane saiki kita ora nganalisa asil gabungan, nanging KEPALA cabang saka panyuwunan narik:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Dadi kita ana ing komitmen bersyarat B3 lan kita kudu njaluk prabédan saka 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

Kanggo nemtokake A3 Ayo nggunakake API GitHub:

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

Kita nggunakake variabel ing ngisor iki sing diwenehake Buddy:

  • $BUDDY_EXECUTION_PULL_REQEUST_NO - nomer panjalukan narik;
  • $BUDDY_REPO_SLUG - kombinasi jeneng pangguna lan gudang (contone max / test).

Saiki ayo simpen pangowahan nggunakake tombol ing ngisor iki lan aktifake analisa panjaluk tarik:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Ora kaya Travis CI, kita ora perlu nemtokake .pvs-studio kanggo caching, wiwit Buddy otomatis caches kabeh file kanggo Bukak sakteruse. Mula, sing terakhir yaiku nyimpen login lan sandhi kanggo PVS-Studio ing Buddy. Sawise nyimpen owah-owahan, kita bakal digawa bali menyang Pipeline. Kita kudu nerusake nyetel variabel lan nambah login lan kunci kanggo PVS-Studio:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Sawise iki, katon saka panjalukan narik anyar utawa commit bakal micu review. Yen komitmen ngemot kesalahan, Buddy bakal nuduhake iki ing kaca panyuwunan tarik.

AppVeyor

Nyetel AppVeyor padha karo Buddy, wiwit kabeh mengkono ing antarmuka web lan ora perlu kanggo nambah file * .yml kanggo gudang project.

Ayo pindhah menyang tab Setelan ing ringkesan proyek:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Ayo gulung mudhun kaca iki lan aktifake nyimpen cache kanggo ngumpulake panjalukan tarik:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Saiki ayo pindhah menyang tab Lingkungan, ing ngendi kita nemtokake gambar kanggo perakitan lan variabel lingkungan sing dibutuhake:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Yen sampeyan wis maca bagean sadurunge, sampeyan ngerti banget karo rong variabel iki - PVS_KEY и PVS_USERNAME. Yen ora, mugi kula ngelingake sampeyan kudu verifikasi lisensi penganalisa PVS-Studio. Kita bakal weruh maneh ing skrip Bash ing mangsa ngarep.

Ing kaca sing padha ing ngisor iki, kita nuduhake folder kanggo caching:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Yen kita ora nindakake iki, kita bakal nganalisa kabeh proyek tinimbang sawetara file, nanging kita bakal entuk output saka file sing ditemtokake. Mulane, penting kanggo ngetik jeneng direktori sing bener.

Saiki wektune kanggo nyoba skrip. Buka tab Tests, pilih Script:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Sampeyan kudu nempel kode ing ngisor iki menyang formulir iki:

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

Ayo menehi perhatian menyang bagean kode ing ngisor iki:

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

Ing assignment rodo tartamtu saka Nilai saka printah pwd kanggo variabel sing kudu nyimpen Nilai standar iki katon aneh ing kawitan marketing, Nanging, aku bakal nerangake kabeh saiki.

Nalika nyiyapake analyzer ing AppVeyor, aku nemoni prilaku penganalisa sing aneh banget. Ing tangan siji, kabeh bisa digunakake kanthi bener, nanging analisis ora diwiwiti. Aku ngginakaken akèh wektu sok dong mirsani sing kita ing / ngarep / appveyor / projects / testcalc / direktori, lan analyzer manteb ing ati sing kita ana ing / opt / appveyor / build-agent /. Banjur aku temen maujud sing $ PWD variabel ngapusi sethitik. Mulane, aku nganyari kanthi manual sadurunge miwiti analisis.

Banjur kabeh kaya sadurunge:

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio
Saiki nimbang fragmen ing ngisor iki:

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"`

Ing kono kita entuk prabédan antarane cabang sing diumumake panjaluk tarik. Kanggo nindakake iki, kita butuh variabel lingkungan ing ngisor iki:

  • $APPVEYOR_PULL_REQUEST_NUMBER — narik nomer panjalukan;
  • $APPVEYOR_REPO_NAME - jeneng pangguna lan repositori proyek.

kesimpulan

Mesthi wae, kita durung nganggep kabeh layanan integrasi sing terus-terusan, nanging kabeh padha duwe spesifik operasi sing padha. Kajaba saka cache, saben layanan nggawe "sepeda" dhewe, supaya kabeh tansah beda.

Nang endi wae, kaya ing Travis-CI, sawetara baris kode lan caching bisa digunakake kanthi sampurna; nang endi wae, kaya ing AppVeyor, sampeyan mung kudu nemtokake folder ing setelan; nanging nang endi wae sampeyan kudu nggawe tombol unik lan nyoba kanggo gawe uwong yakin sistem kanggo menehi sampeyan kesempatan kanggo nimpa pecahan cached. Mulane, yen sampeyan pengin nyiyapake analisis panjalukan tarik ing layanan integrasi terus-terusan sing ora dibahas ing ndhuwur, mula priksa manawa sampeyan ora duwe masalah karo caching.

Matur nuwun kawigatosanipun. Yen soko ora bisa metu, aran gratis kanggo nulis kanggo kita ing dhukungan. Kita bakal menehi saran lan mbantu.

Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio

Yen sampeyan pengin nuduhake artikel iki karo pamirsa sing nganggo basa Inggris, gunakake tautan terjemahan: Maxim Zvyagintsev. Analisis komitmen lan panjaluk tarik ing Travis CI, Buddy lan AppVeyor nggunakake PVS-Studio.

Source: www.habr.com

Add a comment