Pull to refresh

Comments 51

"Так как основная кодовая база у нас на Си, то и писать эту PC утилиту логично тоже прямо на Си. "
Вот не увидел логики. Выполняется утилита на ПК, так что любой язык подойдет.

Вот не увидел логики. 

Дак, найдите её... )

PS. Чтобы что-то увидеть это что-то надо сначала найти.

чтобы что-то найти надо чтобы это что то было, а его нет

Вот не увидел логики.

У человека есть микроскоп и сварочный аппарат. Угадайте, чем он забьет шуруп? ;)

Я просто в файле сборки (rakefile) сделал ADD_DEFINES << "HG_COMMITID=#{val_id.to_i}" а значение брал из вывода hg identify -n. Думаю с гитом и make несложно также сделать.

git не возвращает порядковый номер коммита. Только урезанный хеш.

да, сплошные минусы у него)

Что-то вроде git describe --tags --match 'v[0-9]*' --always работает достаточно похоже. Если мы не стоим на теге, то там есть и порядковый номер коммита (от последнего тега), и хеш. Получается вполне пристойно.

Решил проблему с добавлением коммита в сборку, правда в cmake, но я думаю в make тоже можно.

Берем последний тэг.

execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0
                 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                 OUTPUT_VARIABLE GIT_TAG_NAME)

Берем количество коммитов от последнего тэга.

execute_process(COMMAND ${GIT_EXECUTABLE} rev-list ${GIT_TAG_NAME}..HEAD --count
                 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                 OUTPUT_VARIABLE GIT_COMMIT_AMOUNT)

Юзаем эти переменные в файле, который подтягиваем к проекту

configure_file(gitversion.h.in gitversion.h)


поделюсь своим решением, из cmake, может кому пригодится:

string(TIMESTAMP BUILD_VERSION "%y%j")
MESSAGE("BUILD VERSION ${BUILD_VERSION}")
...
add_definitions(-DBUILD_VERSION=${BUILD_VERSION})
...
set(target "${board}_v${BUILD_VERSION}")
add_executable(${target} ...

Поделюсь еще одним решением. Если проект с гитом, версию контролирую по SHA самого гита

find_program(GIT_EXECUTABLE git)

if (GIT_EXECUTABLE)
    execute_process(COMMAND
            "${GIT_EXECUTABLE}" rev-parse --short HEAD
            WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
            OUTPUT_VARIABLE GIT_SHA1
            ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
    message(STATUS "Can't find Git SCM")
endif()

add_definitions(-DGIT_SHA1="${GIT_SHA1}")

На этом этапе мы полностью знаем, что надо заменить и на что. И тут на сцену выходит культовая консольная утилита sed. Она как раз отлично решает задачу авто замены подстроки в файле. Утилиту sed можно вызваться прямо из консольной утилиты на Си функцией system().

Вроде не пятница. Мы только что сами разобрали файл и теперь для замены вызываем sed. Серьезно?

Выше уже написали, что лучше брать номер версии из системы контроля версий, но вот решение исходной задачи в одну строку не менее культовой утилитой awk:

awk '/^#define\s+SUCCESSFUL_BUILD_NUMBER\s+([[:digit:]]+)/ { printf("%s %s %d\n", $1, $2, $3 + 1); next; } { print $0 }'

Зато в утилиту на Си можно легко добавить автоматическую вставку строки
#define SUCCESSFUL_BUILD_COUNTER 0
в случае, если её не было изначально

В команду для awk добавить ее ничуть не сложнее. Для замены строчки в файле не нужно писать сотню строк на C, и это считая только код в статье — это просто абсурдно сложно для такой простой задачи.

Вот вам еще упрощение. Заводим файл VERSION, содержащий только номер версии. В заголовочном файле делаем:

static const int VERSION =
#include "VERSION"
;

Из задачи увеличения номера убирается весь разбор файла. Увеличение числа делается хоть на cmd.

Если строка уже ищется в коде, что мешает совершить замену кодом на си? Зачем вызывать для этого стороннюю утилиту sed? Если вызывать сторонние утилиты можно, то как заметили выше, можно вообще на си ничего не писать.

Если строка уже ищется в коде, что мешает прямо там и совршить замену?

Заменить подстроку в файле кодом на Си - это сложно.

Хотя выигрыш в том, что не надо было бы устанавливать CygWin.

вызвать sed сложнее чем заменить номер в прочитанной строке и записать её на диск

ну и если бы вы пользовались системой контроля версий то быстро обнаружили бы что корректная сквозная нумерация - не тривиальное дело, и лучше сохранять commit id

если так уж не хочется использовать сторонние приложения (хотя sed при этом всё равно зачем-то вызывается из С), можно было гораздо немногословнее одним только компилятором и обойтись.

version_auto.h:
#ifndef AUTO_VERSION_H
#define AUTO_VERSION_H

#define GIT_BRANCH "main"
#define GIT_LAST_COMMIT_HASH "0d47eb16"
#include "build_counter.h"

#endif

build_counter.h:
#define BUILD_COUNTER 16

inc_counter.c:
#include <stdio.h>
#include "build_counter.h"
void main(){printf("#define BUILD_COUNTER %d",BUILD_COUNTER+1);}

после сборки выполнить:
cc inc_counter.c & inc_counter > build_counter.h

В CMake есть встроенные переменные PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR и PROJECT_VERSION_PATCH - они создаются собственно из PROJECT_VERSION. Эти переменные можно передать в сборку как дефайны через target_compile_definitions (вроде так - лучше перепроверить), а там уже использовать так как вам хочется. Также можно до сборки с помощью вызова из CMake команды git с каким-то флагом узнать хэш коммита (лучше короткий) и его тоже использовать.

да, это там стандартное решение, многие проекты используют. Недостатки, собственно в контексте сабжа:

  • в первом случае менять версию нужно руками,

  • во втором, этот хэш потом ни о чем не говорит всем тем кто фирмварем будет пользоваться, включая сам фирмварь (выше/ниже не проверить, только == )

В моем случае (выше) мы генерим пятизначное число которое по сути дата, (concat год + день года), что по многим причинам удобнее

  • пользователи у клиента могут отследить день билда и сопоставить с остальной коммуникацией (кто, когда, зачем)

  • версии идут по возрастающей, фирмварь может проверить на тему возможного downgrade

  • помещается в 16-бит, и еще 40 лет помещаться будет (мы например FW_VERSION по modbus возвращаем)

PROJECT_VERSION_PATCH можно генерировать из времени последнего коммита - это же CMake, сделать можно почти всё что угодно.

Добавляю к исходникам файлик version.h

Версия автоматически генерится линкером из даты компиляции.

Получается типа 24.02.06

version.h
#ifndef version_h
#define version_h

// Example of __DATE__ string: "Jul 27 2012"
//                              01234567890

//#define BUILD_YEAR_CH0 (__DATE__[ 7])
//#define BUILD_YEAR_CH1 (__DATE__[ 8])
#define BUILD_YEAR_CH2 (__DATE__[ 9])
#define BUILD_YEAR_CH3 (__DATE__[10])

#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n')
#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F')
#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r')
#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p')
#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y')
#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n')
#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l')
#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u')
#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S')
#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O')
#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N')
#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D')

#define BUILD_MONTH_CH0 \
    ((BUILD_MONTH_IS_OCT || BUILD_MONTH_IS_NOV || BUILD_MONTH_IS_DEC) ? '1' : '0')

#define BUILD_MONTH_CH1 \
    ( \
        (BUILD_MONTH_IS_JAN) ? '1' : \
        (BUILD_MONTH_IS_FEB) ? '2' : \
        (BUILD_MONTH_IS_MAR) ? '3' : \
        (BUILD_MONTH_IS_APR) ? '4' : \
        (BUILD_MONTH_IS_MAY) ? '5' : \
        (BUILD_MONTH_IS_JUN) ? '6' : \
        (BUILD_MONTH_IS_JUL) ? '7' : \
        (BUILD_MONTH_IS_AUG) ? '8' : \
        (BUILD_MONTH_IS_SEP) ? '9' : \
        (BUILD_MONTH_IS_OCT) ? '0' : \
        (BUILD_MONTH_IS_NOV) ? '1' : \
        (BUILD_MONTH_IS_DEC) ? '2' : \
        /* error default */    '?' \
    )

#define BUILD_DAY_CH0 ((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')
#define BUILD_DAY_CH1 (__DATE__[ 5])

const char Version[] = {
  BUILD_YEAR_CH2, BUILD_YEAR_CH3, '.', BUILD_MONTH_CH0, BUILD_MONTH_CH1, '.', BUILD_DAY_CH0, BUILD_DAY_CH1,'\0'
};

#endif

ИДЕАЛЬНО!

Если добавить ещё чч:мм:сс то вообще шоколад.

В некоторых случаях будет достаточно отслеживать не дату пуша коммита, а дату сборки проекта. Для этих целей есть макросы DATE и TIME:

LOG_INFO(FILE_PC, "Build: %s %s", __DATE__, __TIME__);
// Выведет: "Build: Feb  6 2024 23:53:52"

Да. Этим можно даже инициализировать часы реального времени RTC, если они есть на электронной плате.

Чуть выше "опередили" (С) (по теме использования даты и возможно уточняющего "время" (например по GMT-0)).
Заглянул в статью, как раз чтобы предложить/напомнить, увидел "зачем просто если можно сложно" а затем уже и комментарии.
Просто давно занимаюсь темой CCTV, и там версия FW для камер-регистраторов у очень многих производителей реально больная тема. Понять их логику при названии файла бывает невозможно (особенно если FW было создано для нескольких линеек камер).

Ну а параллельно этому, давно выработал своё "внутреннее правило" для названия папок после фотосъёмок - "ГГГГ.ММ.ДД (тема/мероприятие)"
Для некоторых "тематически профильных" съёмок, можно начинать с ключевого слова, например Вело/Авиа/Туризм и далее всё тоже самое по ГГГГ.ММ.ДД (мероприятие)

А уж как это "прикрутить" для более профильных задач обсуждаемых в статье, это виднее тем кто этим занимается :)

В последней моей разработке около 60 пользователей в локальной сети и надо порой вводить новые фичи. Пользователи запускают программу из сетевой папки по ярлыку на рабочем столе. В программе номер версии совпадает с номером версии в настройках бд. Если номера версий не совпадают, то при работе пользователю постоянно приходит сообщение что нужно перезагрузить программу, так как она обновлена и некоторые функции будут недоступны. Версия ПО включает номер билда в виде набора цифр даты и времени через точку и может генерироваться автоматически. Скрипт обновления просто переименовывать старый файл exe и записывает новый и меняет версию в бд.

version.h.in + cmake configure_file + DRONE_BUILD_NUMBER

Собирать прошивку для прода на локальном компе у разработчика - несколько моветон. Кто знает, что он у себя наворотил, с какой версией CMake, с каким gcc собирал.

Задача выше решается сборкой на условном гитхаб-экшене и подстановкой седом версии из тега и/или инкрементальным номером сборки

Золотые слова.

Я 3 раза пробовал убедить организацию купить отдельный nettop с Win10 для сборочной фермы.

Уже как 3 года не получаю одобрения.

Значит не убедили. Начальство можно понять - зачем чинить то что и так работает? 3 года, достаточно чтобы убедиться что сборочная ферма не нужна.

 3 года, достаточно чтобы убедиться что сборочная ферма не нужна.

Обоснование тут
DevOps для производства Firmware
https://habr.com/ru/articles/656449/

Обосновать можно даже необходимость кальяна в офисе ;) Но начальство наблюдает за работой офиса 3 года, видит что и без кальяна задачи выполняются.

Можно прикинуть. Например, если замена компа на более быстрый и со вторым монитором экономит примерно час в день, то при скромной зарплате в 200К (с налогами) за 3 года экономия на рабочем времени разработчика составит порядка 750К. И в обоих случаях задачи выполняются.

На практике по разному бывает. В одной конторе я долго просил поменять лампочки в помещении и меня завтраками кормили, а когда я сам поменял мне сказали что теперь контора на электричестве разорится, но так и быть мы тебя не уволим за растраты и самоуправство. Я не стал их мучить своим присутствием и устроился в другое место. Там мне сказали составить список необходимого оборудования. На вопрос про ограничения повторили слово "необходимое". И молча купили оборудования на 2М примерно.

Например, если замена компа на более быстрый и со вторым монитором экономит примерно час в день

и работнику платят не почасово, то это неубедительный аргумент ;)

Виртуальную машину с Линуксом тоже не могут выделить?

Тут виртуальная машина не очень подойдет.
Просто собирать артефакты - этого мало.
Тут cross компиляция.

К NetTop можно хотя бы электронные платы подключить и по UART загрузчиком накатывать свежие сборки для прогона модульных тестов.

Финальная цель - чтобы в run-time всё работало корректно. А это могут показать модульные тесты.

Линукс умеет в кросс-компиляцию и пробрасывать usb-устройства

У людей все настроено под Windows, они не хотят превозмогать.

Потом нам часто для написания прошивок приходится CAD систему Altium Designer открывать для анализа топологии электронных плат.
На Linux Altium Designer не ставят.

Сборочный сервер должен заниматься сборкой, а не служить рабочей машиной.

А как это все соотносится с системой контроля версий? В смысле: забираю я из git ПОСЛЕДНИЙ срез. И после компиляции, у меня "магическим" образом - идет не совпадение версий прошивки. То есть я не могу быть уверенным, что у меня залита последняя версия. Это первая проблема.

Вторая проблема, я и "Вася" заливаем в git свои изменения. И у нас ВСЕГДА будет конфликт слияния.

Если сборки могут делаться в разных местах - значит нужно общий генератор номеров версий. Можно на jenkins сделать ;)

Хорошее замечание.

Тут следует уточнить, что в России в 95% случаев прошивки для микроконтроллеров как правило разрабатывает и сопровождает один solo разработчик.

Если вы собираете в IDE - поищите в настройках, там может найтись автоинкремент версий. В старинных Delphi такое было.

Если собираете на lenkins - у вас там есть специальная переменная.

cmake сам умеет в арифметику, sed и костыли на С не нужны.

Ну, и если в версии закодирован какой то осмысленный набор фич - ее нужно задавать осмысленно, руками ;). Хотя можно и build_id, и source_id, если достаточно просто различать сборки.

Возможно я не понял, возможно меня. Оставим в стороне случай, когда разработчика 2.

собрал я прошивку, и номер в билд подставился 10. Залил ее, и ушел на обед.... Возвращаюсь, и хочу проверить, а последняя ли версия у меня залита в контроллер. Ничего не меняя в коде, я, при сборке, получу уже совсем другой номер. И, естественно, у меня не будет совпадения с контроллером. И как тогда быть?

Именно этот момент меня останавливает в использовании автономерации версий. А так было бы как минимум интересно, сколько раз скомпилил (в пределах версии), до выкатки версии...

git rev-parse --short HEAD вместо версии например. Все зависит от того, что вы хотите получить от версионирования.

Ничего не меняя в коде, я, при сборке, получу уже совсем другой номер. И, естественно, у меня не будет совпадения с контроллером. И как тогда быть?

Да. Есть такое.
Это, своего рода, плата за автоматизацию.

Можно посчитать количество коммитов в мастер git rev-list --count HEAD master

qt creator - просто взять и добавить шаг компиляции, который что-то сгенерирует. Например, хидер с обновленной версией.

Sign up to leave a comment.

Articles