Как вы отслеживаете счет сборки вашей библиотеки, когда несколько авторов используют контроль версий?

Я не знаю, является ли это что-то общее для людей или нет, но я лично всегда отслеживаю количество раз, когда я построил свой код. То есть, как количество раз, когда я вызывал make, так и количество попыток сборки.

Мое текущее решение

У меня есть простой код, который принимает файл как параметр, открывает его, увеличивает число внутри и перезаписывает его. Этот код скомпилирован, во-первых, когда вызывается make.

Сразу после этого вызывается ./increase_build build.txt, который увеличивает количество раз, когда я вызывал make, для создания библиотеки.

Затем код компилируется и создается файл lib (с ar cq ...). После этого вызывается ./increase_build libbuild.txt, который увеличивает количество успешных сборок. Наконец, тесты построены.

Это является примером одного из моих Make файлов.

Почему я был обеспокоен

Это всегда работало нормально, пока я не начал использовать контроль версий. Казалось, что нет проблем: я единственный автор моих собственных библиотек, и я добавляю функции по одному.

В один прекрасный день я тестировал ветвление и слияние (я использую git для себя и svn на работе), поэтому я добавил одну особенность в ветку и кое-что изменил в master, и я объединил их. Теперь файлы подсчета сборки имеют разные значения.

Проблема

Проблема заключается в том, что, скажем, во время ветвления, счет сборки равен 100. Затем я пишу что-то в ветки, а счет сборки доходит до 110. Я пишу что-то в мастер-ветке, а счет сборки достигает 120. Когда Я объединять два, я вижу, что один из них 110, а один - 120 (что, кстати, является конфликтом). Правильным решением было бы установить сборку на 130.

Однако я не могу (читай, что я не хочу) вернуться к фиксации, где ветка взлетела и обнаружила, что она равна 100, поэтому я вычисляю 100+ (110-100) + (120-100) = 130! Я хочу, чтобы это было автоматически

Вопрос

Хорошо, вопрос очевиден: как мне это сделать? Как я могу отслеживать счет сборки (не считать количество!), Когда я работаю с контролем версий? Мне не нужен ответ, основанный на функции в управлении версиями, потому что проблема возникает снова, если я меняю систему управления версиями.

Что бы я мог работать, это добавить одну строку в файле подсчета сборки для каждой сборки, что-то сказать с данными и временем. Тогда номером сборки будет количество строк в файлах подсчета сборки. Кроме того, если я не получаю две сборки на двух ветвях, которые были выполнены ТОЧНО, то слияние файлов будет просто объединением двух.

Интересно, есть ли какие-нибудь лучшие решения? Является ли то, что я хочу (счетчик построек), даже стоит усилий?

P.S. Если вам интересно, почему я делаю это как с количеством сборок, так и с числом успешных сборок, это просто что-то личное. Мне нравится видеть, сколько перестроек я получаю за небольшие опечатки и ошибки, которые я делаю, когда я код.

Изменить: Я программирую на C и С++, поэтому решение для меня работает.

Ответ 1

Я собираюсь опубликовать и принять реализацию моей собственной идеи в качестве ответа, поскольку это кажется наиболее практичным.

Итак, решение:

  • С каждой сборкой добавьте одну строчку в файл сборки, содержащий следующие данные:
    • Дата
    • Время
    • Случайное число
  • С каждым слиянием сохраняйте строки из обоих файлов сборки
  • Количество построений - это общее количество строк в файле сборки.

Каждая строка файла сборки должна быть уникальной. Дата и время делают это очень уникальным. Очень маловероятно, что два человека одновременно создают сборку на своем филиале. Однако это может произойти. Поэтому случайное число генерируется и добавляется для уменьшения этой вероятности.

Однако есть одна проблема. Если вы уронили srand с помощью time(NULL), то, поскольку обе сборки предположительно в одно и то же время, сгенерированное число также может оказаться одинаковым. Поэтому генератор случайных чисел может быть засеян другим числом, например clock() или миллисекундной частью gettimeofday(). Даже если они не генерируются случайным образом, эти числа могут быть помещены вместо случайного числа.

В случае, когда все еще две строки совпадают, я применил страусиный алгоритм.

Update:

Я реализовал его, и все работает нормально. Наконец, я использовал clock_gettime(CLOCK_MONOTONIC, ...) и напечатал наносекунды, полученные этой функцией как случайное число. Причина, по которой я не использовал clock(), заключалась в том, что, поскольку программа была довольно короткой, она меньше, чем разрешение clock(), и поэтому я продолжал получать 0s.

Update:

Вот последний код, который я написал (некоторые части его украли из другого места!). Вам может потребоваться -lrt на некоторых платформах.

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>

struct timespec
{
    long tv_sec;
    long tv_nsec;
};

/* Note: I copy-pasted this from internet (https://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows/5404467#5404467)
 * I tweaked it to return nanoseconds instead of microseconds
 * It is much more complete than just finding tv_nsec, but I'm keeping it for possible future use. */
LARGE_INTEGER getFILETIMEoffset(void)
{
    SYSTEMTIME s;
    FILETIME f;
    LARGE_INTEGER t;

    s.wYear = 1970;
    s.wMonth = 1;
    s.wDay = 1;
    s.wHour = 0;
    s.wMinute = 0;
    s.wSecond = 0;
    s.wMilliseconds = 0;
    SystemTimeToFileTime(&s, &f);
    t.QuadPart = f.dwHighDateTime;
    t.QuadPart <<= 32;
    t.QuadPart |= f.dwLowDateTime;
    return t;
}

int clock_gettime(int X, struct timespec *tv)
{
    LARGE_INTEGER t;
    FILETIME f;
    double microseconds;
    static LARGE_INTEGER offset;
    static double frequencyToNanoseconds;
    static int initialized = 0;
    static BOOL usePerformanceCounter = 0;

    if (!initialized)
    {
        LARGE_INTEGER performanceFrequency;
        initialized = 1;
        usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency);
        if (usePerformanceCounter)
        {
            QueryPerformanceCounter(&offset);
            frequencyToNanoseconds = (double)performanceFrequency.QuadPart/1000000000.0;
        }
        else
        {
            offset = getFILETIMEoffset();
            frequencyToNanoseconds = 0.010;
        }
    }
    if (usePerformanceCounter)
        QueryPerformanceCounter(&t);
    else
    {
        GetSystemTimeAsFileTime(&f);
        t.QuadPart = f.dwHighDateTime;
        t.QuadPart <<= 32;
        t.QuadPart |= f.dwLowDateTime;
    }

    t.QuadPart -= offset.QuadPart;
    microseconds = (double)t.QuadPart/frequencyToNanoseconds;
    t.QuadPart = microseconds;
    tv->tv_sec = t.QuadPart/1000000000;
    tv->tv_nsec = t.QuadPart%1000000000;
    return 0;
}

#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 0       /* not used anyway */
#endif
#endif

int main(int argc, char **argv)
{
    time_t now_sec;
    struct tm *now;
    FILE *bout;
    struct timespec now_clk;
    if (argc < 2)
    {
        printf("Usage: %s build_file_name\n\n", argv[0]);;
        return EXIT_FAILURE;
    }
    bout = fopen(argv[1], "a");
    if (!bout)
    {
        printf("Could not open file: %s\n\n", argv[1]);
        return EXIT_FAILURE;
    }
    time(&now_sec);
    now = gmtime(&now_sec);
    fprintf(bout, "%02d/%02d/%04d %02d:%02d:%02d", now->tm_mday, now->tm_mon+1, now->tm_year+1900, now->tm_hour, now->tm_min, now->tm_sec);
    clock_gettime(CLOCK_MONOTONIC, &now_clk);
    fprintf(bout, " %ld\n", now_clk.tv_nsec);
    return EXIT_SUCCESS;
}

Надеюсь, это поможет кому-то.

Update

После ~ 9 месяцев использования этого, я могу сказать, что это было очень полезно. Некоторые наблюдения:

  • В Windows последний элемент, заданный реализацией clock_gettime, довольно мал, половина времени с тем же значением. Тем не менее, он все еще делает его несколько более случайным.
  • В Linux последний элемент действительно довольно случайный.
  • Время от времени мне приходилось совершать "сборку", чтобы получить строки в файлах сборки, чтобы я мог объединиться. Однако этого можно избежать с помощью git stash.
  • Использование этого почти всегда приводит к конфликту при слиянии, но очень тривиально его разрешать (просто удалите маркеры diff, так как нужны строки из обоих файлов).
  • wc -l является вашим другом.

Ответ 2

Поскольку номер сборки не является особенностью ветки, в которой вы находитесь, ее следует отслеживать по-разному. Я не использую git, но для SVN у нас есть система, которая строит конкретную ветвь, копируя ее в определенный тег, добавляя некоторые артефакты, специфичные для этого тега (ваш номер сборки будет ярким примером вид вещи, которую нужно добавить) и совершение только в том случае, если сборка выполнена успешно.

Другими словами, есть назначенное место (имя тега в SVN, или это может быть отдельный репо), где вы только создаете, и это единственное место, где вы строите, и именно там номер сборки информация сохраняется и обновляется. Ваша сборка script будет выглядеть примерно как

# I don't know git -- this is all very much pseudocode

# Where did you commit the code you want to build?
source=git://server/path/to/my/branch

# Replace builddir tree with yours
git replace git://server/special/place/build/thisproject with code from $source

cd /tmp
git checkout git://sever/special/place/build/thisproject into new builddir
cd builddir

update local version-controlled file buildnumber+=1

if make
    # Build was successful
    git commit buildnumber
    copy build artefacts to where-ever
endif

cd /tmp
rm -rf /tmp/builddir      

Существует состояние гонки; если кто-то проверяет в запросе на сборку после вашего, но почему-то заканчивает работу с сервером первым, вы в конечном итоге строит свою регистрацию.

Это, вероятно, можно сделать намного проще, используя назначенный узел сборки, например, с Hudson/Jenkins.

Ответ 3

Ваше решение с журналом сборки (одна строка для каждой сборки) кажется довольно умным. Вы можете добавить IP (или MAC-адрес) машины, выполняющей сборку, timemample, чтобы удалить риск дублирования строки. Однако в зависимости от вашего VCS вам, вероятно, придется вручную объединить файл журнала сборки. С помощью git вы можете настроить его, чтобы слияние всегда сохраняло обе версии (и в конечном итоге сортировать строки по дате и т.д.).

Ответ 4

Я не могу (читать, я не хочу) вернуться к коммиту, где ветка взлетела, и найти, что ей было 100, поэтому я вычисляю... Я хочу, чтобы это было автоматически

Грязная идея: каждая сборка добавляет строку в файл с версией (или один из пары PASS/FAIL в зависимости от результата), немного отличающийся для каждой ветки. Для слияния веток требуется ручное слияние этого сигнального файла (файлов), где различия в оформлении строк упрощают эту задачу. wc -l позже будет считать числа