Comments 51
"Так как основная кодовая база у нас на Си, то и писать эту PC утилиту логично тоже прямо на Си. "
Вот не увидел логики. Выполняется утилита на ПК, так что любой язык подойдет.
Я просто в файле сборки (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 возвращаем)
Добавляю к исходникам файлик 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"
Чуть выше "опередили" (С) (по теме использования даты и возможно уточняющего "время" (например по 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-устройства
А как это все соотносится с системой контроля версий? В смысле: забираю я из git ПОСЛЕДНИЙ срез. И после компиляции, у меня "магическим" образом - идет не совпадение версий прошивки. То есть я не могу быть уверенным, что у меня залита последняя версия. Это первая проблема.
Вторая проблема, я и "Вася" заливаем в git свои изменения. И у нас ВСЕГДА будет конфликт слияния.
Если вы собираете в IDE - поищите в настройках, там может найтись автоинкремент версий. В старинных Delphi такое было.
Если собираете на lenkins - у вас там есть специальная переменная.
cmake сам умеет в арифметику, sed и костыли на С не нужны.
Ну, и если в версии закодирован какой то осмысленный набор фич - ее нужно задавать осмысленно, руками ;). Хотя можно и build_id, и source_id, если достаточно просто различать сборки.
Возможно я не понял, возможно меня. Оставим в стороне случай, когда разработчика 2.
собрал я прошивку, и номер в билд подставился 10. Залил ее, и ушел на обед.... Возвращаюсь, и хочу проверить, а последняя ли версия у меня залита в контроллер. Ничего не меняя в коде, я, при сборке, получу уже совсем другой номер. И, естественно, у меня не будет совпадения с контроллером. И как тогда быть?
Именно этот момент меня останавливает в использовании автономерации версий. А так было бы как минимум интересно, сколько раз скомпилил (в пределах версии), до выкатки версии...
git rev-parse --short HEAD вместо версии например. Все зависит от того, что вы хотите получить от версионирования.
Ничего не меняя в коде, я, при сборке, получу уже совсем другой номер. И, естественно, у меня не будет совпадения с контроллером. И как тогда быть?
Да. Есть такое.
Это, своего рода, плата за автоматизацию.
Можно посчитать количество коммитов в мастер git rev-list --count HEAD master
qt creator - просто взять и добавить шаг компиляции, который что-то сгенерирует. Например, хидер с обновленной версией.
Автоматическое Обновление Версии Прошивки