Как я могу получить свой код C для автоматической распечатки хэша Git-версии?

Есть ли простой способ написания кода на C, который может получить доступ к хэш-версии Git?

Я написал программное обеспечение на С для сбора научных данных в лабораторных условиях. Мой код записывает данные, которые он собирает в файл.yaml для последующего анализа. Мои эксперименты меняются изо дня в день, и мне часто приходится модифицировать код. Чтобы отслеживать ревизии, я использую git-репозиторий.

Я хотел бы иметь возможность включать хэш редакции Git в качестве комментария в мои файлы данных.yaml. Таким образом, я мог бы посмотреть на файл.yaml и точно знать, какой код использовался для генерации данных, показанных в этом файле. Есть ли простой способ сделать это автоматически?

14 ответов

Решение

В моей программе я храню номер версии git и дату сборки в отдельном файле, который называется version.c, который выглядит так:

#include "version.h"
const char * build_date = "2009-11-10 11:09";
const char * build_git_sha = "6b54ea36e92d4907aba8b3fade7f2d58a921b6cd";

Также есть заголовочный файл, который выглядит так:

#ifndef VERSION_H
#define VERSION_H
extern const char * build_date; /* 2009-11-10 11:09 */
extern const char * build_git_sha; /* 6b54ea36e92d4907aba8b3fade7f2d58a921b6cd */
#endif /* VERSION_H */

И заголовочный файл, и файл C генерируются скриптом Perl, который выглядит следующим образом:

my $git_sha = `git rev-parse HEAD`;
$git_sha =~ s/\s+//g;
# This contains all the build variables.
my %build;
$build{date} = make_date_time ();
$build{git_sha} = $git_sha;

hash_to_c_file ("version.c", \%build, "build_");

Вот hash_to_c_file делает всю работу по созданию version.c а также version.h а также make_date_time делает строку как показано.

В основной программе у меня рутина

#include "version.h"

// The name of this program.
const char * program_name = "magikruiser";
// The version of this program.
const char * version = "0.010";

/* Print an ID stamp for the program. */

static void _program_id_stamp (FILE * output)
{
    fprintf (output, "%s / %s / %s / %s\n",
             program_name, version,
             build_date, build_git_sha);
}

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

Если вы используете сборку на основе make, вы можете поместить это в Makefile:

GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)

(Смотрите man git опишите, что делают переключатели)

затем добавьте это к своим CFLAGS:

-DVERSION=\"$(GIT_VERSION)\"

Тогда вы можете просто ссылаться на версию прямо в программе, как если бы это был #define:

printf("Version: %s\n", VERSION);

По умолчанию это просто печатает сокращенный идентификатор git commit, но при желании вы можете пометить определенные выпуски чем-то вроде:

git tag -a v1.1 -m "Release v1.1"

тогда он распечатает:

Version: v1.1-2-g766d

это означает, что 2 коммитов старше v1.1, с идентификатором git commit, начинающимся с "766d".

Если в вашем дереве есть незафиксированные изменения, к нему добавится "-dirty".

Нет сканирования зависимостей, поэтому вы должны сделать явный make clean заставить версию обновляться. Это может быть решено, однако.

Преимущества в том, что он прост и не требует никаких дополнительных сборочных зависимостей, таких как perl или awk. Я использовал этот подход с GNU automake и со сборками Android NDK.

В итоге я использовал нечто очень похожее на ответ @Kinopiko, но вместо perl использовал awk. Это полезно, если вы застряли на компьютерах с Windows, на которых по умолчанию установлен awk, но не на perl. Вот как это работает.

В моем make-файле есть строка, которая вызывает git, date и awk для создания ac-файла:

$(MyLibs)/version.c: FORCE 
    $(GIT) rev-parse HEAD | awk ' BEGIN {print "#include \"version.h\""} {print "const char * build_git_sha = \"" $$0"\";"} END {}' > $(MyLibs)/version.c
    date | awk 'BEGIN {} {print "const char * build_git_time = \""$$0"\";"} END {} ' >> $(MyLibs)/version.c 

Каждый раз, когда я компилирую свой код, команда awk генерирует файл version.c, который выглядит следующим образом:

/* version.c */
#include "version.h"
const char * build_git_sha = "ac5bffc90f0034df9e091a7b3aa12d150df26a0e";
const char * build_git_time = "Thu Dec  3 18:03:58 EST 2009";

У меня есть статический файл version.h, который выглядит так:

/*version.h*/
#ifndef VERSION_H_
#define VERSION_H_

extern const char * build_git_time;
extern const char * build_git_sha;


#endif /* VERSION_H_ */

Остальная часть моего кода теперь может получить доступ к времени сборки и хешу git, просто включив заголовок version.h. Чтобы закончить все это, я говорю git игнорировать version.c, добавив строку в мой файл.gitignore. Таким образом, git не дает мне постоянных конфликтов слияния. Надеюсь это поможет!

Ваша программа может раскошелиться на git describeлибо во время выполнения, либо как часть процесса сборки.

Есть две вещи, которые вы можете сделать:

  • Вы можете заставить Git вставлять некоторую информацию о версии в файл для вас.

    Более простой способ заключается в использовании ident атрибут, который означает положить (например)

    *.yaml    ident
    

    в .gitattributes файл и $Id$ в соответствующем месте. Он будет автоматически расширен до идентификатора SHA-1 содержимого файла (идентификатор blob): это НЕ версия файла или последний коммит.

    Git поддерживает ключевое слово $Id$ таким образом, чтобы не трогать файлы, которые не были изменены во время переключения веток, перемотки веток и т. Д. Если вы действительно хотите, чтобы Git поместил в файл идентификатор или описание фиксации (версии), вы можете (ab) использовать filter с помощью фильтра clean / smudge, чтобы развернуть какое-либо ключевое слово (например, $Revision$) при оформлении заказа и очистить его для фиксации.

  • Вы можете сделать процесс сборки таким же образом, как ядро ​​Linux или Git.

    Посмотрите на скрипт GIT-VERSION-GEN и его использование в Git Makefile, или, например, как этот Makefile внедряет информацию о версии во время генерации / конфигурации gitweb/gitweb.cgi файл.

    GIT-VERSION-GEN использует git description для генерации описания версии. Он должен работать лучше, если вы помечаете (используя подписанные / аннотированные теги) выпуски / вехи вашего проекта.

Когда мне нужно сделать это, я использую тег, как RELEASE_1_23, Я могу решить, что это за метка, не зная SHA-1. Я фиксирую затем тег. Вы можете сохранить этот тег в своей программе так, как вам нравится.

Основываясь на ответе njd27, я использую версию со сканированием зависимостей в сочетании с файлом version.h со значениями по умолчанию для случаев, когда код построен другим способом. Все файлы, которые включают version.h, будут перестроены.

Он также включает дату пересмотра в качестве отдельного определения.

# Get git commit version and date
GIT_VERSION := $(shell git --no-pager describe --tags --always --dirty)
GIT_DATE := $(firstword $(shell git --no-pager show --date=short --format="%ad" --name-only))

# recompile version.h dependants when GIT_VERSION changes, uses temporary file version~
.PHONY: force
version~: force
    @echo '$(GIT_VERSION) $(GIT_DATE)' | cmp -s - $@ || echo '$(GIT_VERSION) $(GIT_DATE)' > $@
version.h: version~
    @touch $@
    @echo Git version $(GIT_VERSION) $(GIT_DATE)

Это решение для проекта CMake, которое работает для Windows и Linux, без необходимости установки каких-либо других программ (например, языков сценариев).

Хэш git записывается в файл.h с помощью сценария, который является сценарием bash при компиляции в Linux или пакетным сценарием Windows при компиляции в Windows, а предложение if в CMakeLists.txt выбирает сценарий, соответствующий платформе, код компилируется.

Следующие 2 сценария сохраняются в том же каталоге, что и CMakeLists.txt:

get_git_hash.sh:

#!/bin/bash
hash=$(git describe --dirty --always --tags)
echo "#ifndef GITHASH_H" > include/my_project/githash.h
echo "#define GITHASH_H" >> include/my_project/githash.h
echo "const std::string kGitHash = \"$hash\";" >> include/my_project/githash.h
echo "#endif // GITHASH_H" >> include/my_project/githash.h

get_git_hash.cmd:

@echo off
FOR /F "tokens=* USEBACKQ" %%F IN (`git describe --dirty --always --tags`) DO (
SET var=%%F
)
ECHO #ifndef GITHASH_H > include/my_project/githash.h
ECHO #define GITHASH_H >> include/my_project/githash.h
ECHO const std::string kGitHash = "%var%"; >> include/my_project/githash.h
ECHO #endif // GITHASH_H >> include/my_project/githash.h

В CMakeLists.txt добавлены следующие строки

if(WIN32)
  add_custom_target(
    run ALL
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    COMMAND get_git_hash.cmd
  )
else()
  add_custom_target(
    run ALL
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    COMMAND ./get_git_hash.sh
  )
endif()

include_directories(include)

В код сгенерированный файл включен с #include <my_project/githash.h> и хеш git можно вывести на терминал с помощью std::cout << "Software version: " << kGitHash << std::endl;или записать в файл yaml (или любой другой) аналогичным образом.

Я также использую git для отслеживания изменений в моем научном коде. я не хотел использовать внешнюю программу, потому что она ограничивает переносимость кода (например, если кто-то захочет внести изменения в MSVS).

Мое решение состояло в том, чтобы использовать только основную ветвь для расчетов и заставить ее выводить время сборки, используя макросы препроцессора. __DATE__ а также __TIME__, таким образом я могу проверить это с помощью git log и посмотреть, какую версию я использую. ссылка: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html

Другой элегантный способ решить эту проблему - включить git log в исполняемый файл. сделать объектный файл из журнала git и включить его в код. на этот раз единственная внешняя программа, которую вы используете - это objcopy, но кодирования здесь меньше. ссылка: http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 и встраивать данные в программу на C++

Что вам нужно сделать, это сгенерировать файл заголовка (например, используя echo из строки cmd) примерно так:

#define GIT_HASH \
"098709a0b098c098d0e"

Для генерации используйте что-то вроде этого:

echo #define GIT_HASH \ > file.h
echo " > file.h
echo git status <whatever cmd to get the hash> > file.h
echo " > file.h

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

Мне хотелось чего-то подобного для своих проектов на базе CMake (который я обычно использую для C или C++). Идея заключалась в том, чтобы добавить проект (например, в виде подмодуля) и автоматически получить небольшую статическую библиотеку, которую можно связать с основным проектом. Статическая библиотека содержит простой API C для запроса информации о версиях во время выполнения. Зависимости CMake гарантируют, что сгенерированный исходный файл (который содержит информацию о версиях) всегда будет актуальным. Нет необходимости повторно запускать этап настройки (cmake) каждый раз, когда ваш статус git меняется — он будет обнаружен автоматически. Проект FOSS под лицензией MIT.

Я также создал черновик (все еще находящийся в банкомате филиала) того, как интегрироваться с Doxygen. Для этого необходимо добавить привязку к запуску Doxygen и установить версию в документации Doxygen на версию, полученную через Git.

Проект можно найти здесь:

... и пример проекта здесь:

Основные шаги по настройке проекта:

      # add the project (or your fork) as submodule (f.e. in the folder modules):
git submodule add https://github.com/boon-code/c-git-version modules/c-git-version

CMakeLists.txt может выглядеть так:

      cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(Example C)

add_subdirectory(modules/c-git-version)

add_executable(example "${CMAKE_CURRENT_SOURCE_DIR}/src/main.c")
target_link_libraries(example c_git_version)

С помощью простого файла C «main.c», который выводит информацию о версии:

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

#include "c_git_version.h"

int main(void)
{
        printf("Version: %s\nBranch: %s\nHash: %s\n",
                c_git_version(), c_git_branch(), c_git_hash());

        printf("Submodules:\n%s\n", c_git_submodules());

        return EXIT_SUCCESS;
}

Полное дерево файлов проекта будет выглядеть так:

      .
|-- CMakeLists.txt
|-- modules
|   `-- c-git-version
|       |-- CMakeLists.txt
|       |-- LICENSE
|       |-- README.md
|       |-- cmake
|       |   `-- GetGitVersion.cmake
|       |-- inc
|       |   `-- c_git_version.h
|       `-- src
|           `-- c_git_version.c.in
`-- src
    `-- main.c

Еще один вариант, основанный на Makefile и shell

GIT_COMMIT_FILE=git_commit_filename.h

$(GIT_COMMIT_FILE): phony
    $(eval GIT_COMMIT_SHA=$(shell git describe --abbrev=6 --always 2>/dev/null || echo 'Error'))
    @echo SHA=$(GIT_COMMIT_SHA)
    echo -n "static const char *GIT_COMMIT_SHA = \"$(GIT_COMMIT_SHA)\";" > $(GIT_COMMIT_FILE)

Файл git_commit_filename.h будет заканчиваться одной строкой, содержащей статическое const char *GIT_COMMIT_SHA="";

С https://gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406

Это не для кода C, но я опубликую его только для того, чтобы завершить ответы. Может быть, Java-парень сможет это использовать. Если вы используете Maven в качестве системы сборки, я успешно использовал плагин Git commit ID .

Использовать плагин очень просто , просто добавьте приведенный ниже код в ваш pom.xml:

                  <plugin>
                <groupId>io.github.git-commit-id</groupId>
                <artifactId>git-commit-id-maven-plugin</artifactId>
                <version>5.0.0</version>
                <executions>
                    <execution>
                        <id>get-the-git-infos</id>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                        <phase>initialize</phase>
                    </execution>
                </executions>
                <configuration>
                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
                    <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
                    <includeOnlyProperties>
                        <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
                        <includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty>
                    </includeOnlyProperties>
                    <commitIdGenerationMode>full</commitIdGenerationMode>
                </configuration>
            </plugin>

Вы можете видеть, как я сделал это для memcached в оригинальном коммите.

В основном, отмечайте время от времени, и удостоверьтесь, что вещь, которую Вы поставляете, прибывает из make dist или похожие.

Другие вопросы по тегам