Собираем данные по CI популярных проектов на GitHub’е

В школе нас учили, что «списывать — не хорошо». Однако перенимать чужой опыт — очень даже хорошо. Вот и посмотрим, что и как делают люди на своих проектах для CI. Я буду рассматривать интеграцию с помощью TravisCI. Для начала сделаем фильтр для нужных проектов на ГитХабе. Берем те, где последний коммит был не позже полугода назад и где больше 100 «звездочек» (да, совсем молодые и набирающие популярность проекты не попадут в выборку). Использую API для поиска репозиториев (ссылка на документацию), получаем список нужных проектов. Конфиг-файл Тревиса называется .travis.yml. Скачаем его для интересующих нас проектов. Надо иметь ввиду, что не все используют Тревис. Так что, 404 будет выпадать часто. Получить просто код файла (raw) можно через домен https://raw.githubusercontent.com/. Ссылка должна быть вида:

'https://raw.githubusercontent.com/' . full_name . '/' . branch . '/.travis.yml';

Тут full_name — это имя автора проекта и название проекта (например, kronusme/dota2-api), а branch — это название интересующей ветки. Ветка нам нужна та, которая «дефолтная». Это значение будет в ответе на запрос API по поиску репозиториев (поле — default_branch). Сам файл с конфигом можно получить и обычным wget’ом.

Возникает вопрос — «куда и в каком виде его сохранить»? Распарсить ли его сразу или просто сохранить как строку? Хранить в БД или просто сделать много-много файликов? Я выбрал вариант парсить файлы сразу после загрузки с сервера и сохранять результат в БД. Реляционная СУБД тут не очень подойдет, а вот какая-то документо-ориентированная — в самый раз. Решил взять MongoDB (драйвер для PHP можно взять здесь).

Чем парсить yml? У Symfony для этих целей есть отличный компонент — symfony/yaml. Я использовал composer, так что подключить этот компонент не составило труда:

{
    "require": {
        "symfony/yaml": "~2.1"
    }
}

Неплохо было бы взять что-то для отправки запросов. Выпал пал на rmccue/requests. Файл composer.json получился такой:

{
    "name": "repo-travis-parser",
    "author": "KronuS Me",
    "require": {
        "symfony/yaml": "~2.1",
        "rmccue/requests": ">=1.0",
        "pear/console_table": "*"
    }
}

pear/console_table — небольшой вспомогательный класс для табличного вывода данных в консоли.

Выполняем «composer install» перед началом работы и не забываем подключить vendor/autoload.php в нужных файлах. Код самого маппера приведен ниже:

<?php
 
require 'vendor/autoload.php';
 
$langs = ['PHP'];
 
$m = new MongoClient();
$travis = $m->selectDB('travis');
$travis->createCollection('configs');
 
$pages = 10;
$pageSize = 100;
$stars = 100;
 
foreach ($langs as $lang) {
    for ($page = 1; $page <= $pages; $page++) {
        $r = Requests::get('https://api.github.com/search/repositories?q=language%3A' . $lang . '+stars%3A>%3D' . $stars . '+pushed%3A>' . date('Y-m-d',
                strtotime('-6 months')) . '&per_page=' . $pageSize . '&page=' . $page);
        $json = json_decode($r->body);
        foreach ($json->items as $repo) {
            $travisConfigUrl = 'https://raw.githubusercontent.com/' . $repo->full_name . '/' . $repo->default_branch . '/.travis.yml' . "\n";
            $travisConfig = Requests::get($travisConfigUrl);
            if ($travisConfig->status_code > 299) {
                echo $repo->full_name . ' without .travis.yml' . "\n";
            } else {
                try {
                    $parser = new \Symfony\Component\Yaml\Parser();
                    $document = $parser->parse($travisConfig->body);
                    $document['github_name'] = $repo->full_name;
                    $travis->configs->insert($document);
                    echo $repo->full_name . ' saved' . "\n";
                } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
                    echo $repo->full_name . ' not parsed!' . "\n";
                }
            }
        }
    }
    echo $lang . ' loaded' . "\n";
}

В самом начале создаем в Монго нужную нам БД и коллекцию в ней. $langs — это массив интересующих нас языков. В нем можно вписать любые интересующие — JavaScript, Ruby, Python, Java и т.д. $pages — кол-во страниц, которые берутся из ответа API по каждому языку. $pageSize — кол-во репозиториев на одной странице ответа. Сам маппер работает в три вложенных цикла. В самом верхнем проходится по языкам. Внутри делаем запрос по языку на нужное количество страниц. Для каждой страницы выполняем проход по репозиториям для выкачки конфиг-файла Тревиса. Стоит отметить, что парсер от Symfony не всегда может корректно проанализировать файл и в таких случаях выбрасывает исключения. Если парсинг прошел успешно, то сохраняем документ в БД (добавляя в него full_name репозитория).

Собирание данных завершено. Теперь время их анализировать. Всего у меня в БД попало 558 конфиг-файлов. Начальные настройки файла с разными запросами (view.php):

<?php
 
require 'vendor/autoload.php';
 
$m = new MongoClient();
$configs = $m->selectDB('travis')->selectCollection('configs');
 
function printTable($data) {
    $table = new Console_Table();
    $table->addData(iterator_to_array($data));
    echo $table->getTable();
}

Для начала посмотрим, кто в качестве language указывает не PHP:

$r = $configs->find(['language' => ['$ne' => 'php', '$exists' => true]], ['github_name', 'language']);
echo 'Repositories with `language` != php: ' . $r->count() . "\n";
printTable($r);
echo "\n";

У меня таких оказалось 8:

+---------+---------------------------------+
| python  | TechEmpower/FrameworkBenchmarks |
| python  | hippyvm/hippyvm                 |
| cpp     | ZoneMinder/ZoneMinder           |
| scala   | foursquare/twofishes            |
| node_js | Wildhoney/Magento-on-Angular    |
| python  | al3x/sovereign                  |
| node_js | niklasvh/php.js                 |
| java    | adamfisk/LittleProxy            |
+---------+---------------------------------+

Далее в скобках указано кол-во репозиториев удовлетворяющих критериям выборки

Репозитории, где версии PHP указаны в разделе php (542):

$r = $configs->find(['php' => ['$exists' => true]], ['github_name', 'php']);
echo 'Repositories with `php`-section: ' . $r->count() . "\n";
echo "\n";

Репозитории, где версии PHP указаны в разделе matrix (5):

$r = $configs->find(['php' => ['$exists' => false], 'matrix' => ['$exists' => true]], ['github_name']);
echo 'Repositories with `matrix`-section and without `php`-section: ' . $r->count() . "\n";
echo "\n";

Посмотрим, кто тестирует свой код на PHP 5.2 (27):

$r = $configs->find(['$or' => [['php' => ['$regex' => '5\.2']], ['php' => 5.2]]], ['github_name']);
echo 'Repositories tested on `php-5.2`: ' . $r->count() . "\n";
printTable($r);
echo "\n";

Почему тут такая странная проверка? Это связано с тем, как был проанализирован конфиг-файл Тревиса. Если версии PHP были указаны без кавычек, то они могут быть восприняты как числа с точкой (пример — 5.2, 5.3, 5.4 и т.д.). Но это не касается более «точных» версий (например, 5.3.3) — они будут сохранены как строки.

Версии 5.3, 5.4, 5.5, 5.6 проверяются аналогично вышеуказанному запросу. Результаты:

+-----+-----+
| 5.3 | 406 |
| 5.4 | 515 |
| 5.5 | 478 |
| 5.6 | 348 |
+-----+-----+

А кому не безразлична HHVM (318):

$r = $configs->find(['php' => ['$regex' => 'hhvm']], ['github_name']);
echo 'Repositories tested on `php-hhvm`: ' . $r->count() . "\n";
echo "\n";

А кто проверяет HHVM, но не парится, если там «что-то пошло не так» (131):

$r = $configs->find(['$or' => [['matrix.allow_failures' => 'hhvm'], ['matrix.allow_failures.php' => 'hhvm']]], ['github_name']);
echo 'Repositories which allow failures on the `php-hhvm`: ' . $r->count() . "\n";
//printTable($r);
echo "\n";

Кто использует composer (420):

$r = $configs->find(['$or' => [
        ['before_script' => ['$regex' => 'composer']],
        ['before_install' => ['$regex' => 'composer']],
        ['install' => ['$regex' => 'composer']],
        ['script' => ['$regex' => 'composer']]
    ]
], ['github_name']);
echo 'Repositories which use `composer.phar`: ' . $r->count() . "\n";
echo "\n";

Кто обновляет composer (156):

$r = $configs->find(['$or' => [
        ['before_script' => ['$regex' => 'composer self-update']],
        ['before_script' => ['$regex' => 'composer selfupdate']],
        ['before_install' => ['$regex' => 'composer self-update']],
        ['before_install' => ['$regex' => 'composer selfupdate']],
        ['install' => ['$regex' => 'composer self-update']],
        ['install' => ['$regex' => 'composer selfupdate']],
        ['script' => ['$regex' => 'composer self-update']],
        ['script' => ['$regex' => 'composer selfupdate']]
    ]
], ['github_name']);
echo 'Repositories which update `composer.phar`: ' . $r->count() . "\n";
echo "\n";

Кто использует notifications (130):

$r = $configs->find(['notifications' => ['$exists' => true]], ['github_name', 'notifications']);
echo 'Repositories with `notifications`-section: ' . $r->count() . "\n";
echo "\n";

Посмотрим детальнее. Кто использует уведомление на почту (99):

$r = $configs->find(['notifications.email' => ['$exists' => true]], ['github_name', 'notifications']);
echo 'Repositories with `notifications.email`-section: ' . $r->count() . "\n";
echo "\n";

А кто по irc (36):

$r = $configs->find(['notifications.irc' => ['$exists' => true]], ['github_name', 'notifications']);
echo 'Repositories with `notifications.irc`-section: ' . $r->count() . "\n";
echo "\n";

Таким нехитрым способом можно собрать довольно много данных о том, что и как используют разработчики при создании своих продуктов.

, , , , , ,

Оставить комментарий

Top ↑ | Main page | Back