Existe-t-il un moyen facile d'écrire du code C qui peut accéder à son hachage de version Git?
J'ai écrit un logiciel en C pour collecter des données scientifiques en laboratoire. Mon code enregistre les données qu'il recueille dans un fichier .yaml pour une analyse ultérieure. Mes expériences changent au jour le jour et je dois souvent modifier le code. Pour garder une trace des révisions, j'utilise un référentiel git.
Je voudrais pouvoir inclure le hachage de révision Git en tant que commentaire dans mes fichiers de données .yaml. De cette façon, je pouvais regarder le fichier .yaml et savoir exactement quel code a été utilisé pour générer les données affichées dans ce fichier. Existe-t-il un moyen simple de le faire automatiquement?
Dans mon programme, je garde le numéro de version de git et la date de la construction dans un fichier séparé, appelé version.c
, qui ressemble à ceci:
#include "version.h"
const char * build_date = "2009-11-10 11:09";
const char * build_git_sha = "6b54ea36e92d4907aba8b3fade7f2d58a921b6cd";
Il existe également un fichier d'en-tête, qui ressemble à ceci:
#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 */
Le fichier d'en-tête et le fichier C sont générés par un script Perl qui ressemble à ceci:
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_");
Ici hash_to_c_file
fait tout le travail de création version.c
et version.h
et make_date_time
crée une chaîne comme indiqué.
Dans le programme principal, j'ai une routine
#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);
}
Je ne connais pas très bien git, donc je serais heureux de recevoir des commentaires s'il y a une meilleure façon de le faire.
Si vous utilisez une construction basée sur la création, vous pouvez la placer dans le Makefile:
GIT_VERSION := "$(Shell git describe --abbrev=4 --dirty --always --tags)"
(Voir man git describe pour ce que font les commutateurs)
puis ajoutez ceci à vos CFLAGS:
-DVERSION=\"$(GIT_VERSION)\"
Ensuite, vous pouvez simplement référencer la version directement dans le programme comme s'il s'agissait d'un #define:
printf("Version: %s\n", VERSION);
Par défaut, cela affiche simplement un identifiant de validation git abrégé, mais vous pouvez éventuellement étiqueter des versions particulières avec quelque chose comme:
git tag -a v1.1 -m "Release v1.1"
alors il imprimera:
Version: v1.1-2-g766d
ce qui signifie, 2 valide après la v1.1, avec un identifiant de validation git commençant par "766d".
S'il y a des changements non validés dans votre arborescence, il ajoutera "-dirty".
Il n'y a pas d'analyse de dépendance, vous devez donc faire une explicite make clean
pour forcer la mise à jour de la version. Ceci peut être résol cependant.
Les avantages sont qu'il est simple et ne nécessite aucune dépendance de construction supplémentaire comme Perl ou awk. J'ai utilisé cette approche avec GNU automake et avec Android builds NDK).
J'ai fini par utiliser quelque chose de très similaire à la réponse de @ Kinopiko, mais j'ai utilisé awk au lieu de Perl. Ceci est utile si vous êtes bloqué sur des machines Windows qui ont installé awk par nature de mingw, mais pas Perl. Voici comment cela fonctionne.
Mon makefile contient une ligne qui appelle git, date et awk pour créer un fichier c:
$(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
Chaque fois que je compile mon code, la commande awk génère un fichier version.c qui ressemble à ceci:
/* version.c */
#include "version.h"
const char * build_git_sha = "ac5bffc90f0034df9e091a7b3aa12d150df26a0e";
const char * build_git_time = "Thu Dec 3 18:03:58 EST 2009";
J'ai un fichier version.h statique qui ressemble à ceci:
/*version.h*/
#ifndef VERSION_H_
#define VERSION_H_
extern const char * build_git_time;
extern const char * build_git_sha;
#endif /* VERSION_H_ */
Le reste de mon code peut désormais accéder au temps de génération et au hachage git en incluant simplement l'en-tête version.h. Pour conclure, je dis à git d'ignorer la version.c en ajoutant une ligne à mon fichier .gitignore. De cette façon, git ne me donne pas constamment de conflits de fusion. J'espère que cela t'aides!
Votre programme peut décortiquer vers git describe
, soit au moment de l'exécution, soit dans le cadre du processus de génération.
Vous pouvez faire deux choses:
Vous pouvez faire Git pour incorporer des informations de version dans le fichier pour vous.
La manière la plus simple est d'utiliser ident
attribut , ce qui signifie mettre (par exemple)
*.yaml ident
dans .gitattributes
fichier et $Id$
à l'endroit approprié. Il serait automatiquement développé en identifiant SHA-1 du contenu du fichier (id blob): c'est PAS la version du fichier ou le dernier commit.
Git prend en charge le mot-clé $ Id $ de cette manière pour éviter de toucher des fichiers qui n'ont pas été modifiés lors du changement de branche, du rembobinage de la branche, etc. filter
attribut, en utilisant un filtre clean/smudge pour développer un mot clé (par exemple $ Revision $) à la caisse, et le nettoyer pour le valider.
Vous pouvez faire un processus de construction pour le faire pour vous, comme le fait le noyau Linux ou Git lui-même.
Jetez un œil au script GIT-VERSION-GEN et à son utilisation dans Git Makefile , ou par exemple comment ce Makefile intègre les informations de version lors de la génération/configuration de gitweb/gitweb.cgi
fichier.
GIT-VERSION-GEN utilise git describe pour générer la description de la version. Il doit fonctionner mieux que vous balisez (à l'aide de balises signées/annotées) les versions/jalons de votre projet.
Lorsque je dois le faire, j'utilise un tag , comme RELEASE_1_23
. Je peux décider ce que le tag peut être sans connaître le SHA-1. J'engage puis tag. Vous pouvez stocker cette balise dans votre programme comme vous le souhaitez.
Sur la base de la réponse de njd27, j'utilise une version avec analyse des dépendances, en combinaison avec un fichier version.h avec des valeurs par défaut lorsque le code est construit d'une manière différente. Tous les fichiers qui incluent version.h seront reconstruits.
Il inclut également la date de révision en tant que définition distincte.
# 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)
J'utilise également git pour suivre les changements dans mon code scientifique. je ne voulais pas utiliser un programme externe car cela limite la portabilité du code (si quelqu'un veut faire des changements sur MSVS par exemple).
ma solution était d'utiliser uniquement la branche principale pour les calculs et de lui faire sortir le temps de construction en utilisant des macros de préprocesseur __DATE__
et __TIME__
. de cette façon, je peux le vérifier avec git log et voir quelle version j'utilise. réf: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
une autre façon élégante de résoudre le problème est d'inclure git log dans l'exécutable. créer un fichier objet à partir de git log et l'inclure dans le code. cette fois, le seul programme externe que vous utilisez est objcopy mais il y a moins de codage. ref: http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 et Intégrer des données dans un programme C++
Ce que vous devez faire est de générer un fichier d'en-tête (par exemple en utilisant l'écho de la ligne cmd) quelque chose comme ceci:
#define GIT_HASH \
"098709a0b098c098d0e"
Pour le générer, utilisez quelque chose comme ceci:
echo #define GIT_HASH \ > file.h
echo " > file.h
echo git status <whatever cmd to get the hash> > file.h
echo " > file.h
Il faudra peut-être jouer un peu avec les guillemets et les barres obliques inverses pour le compiler, mais vous avez l'idée.
Encore une autre variation basée sur Makefile et 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)
Le fichier git_commit_filename.h se terminera avec une seule ligne contenant un caractère statique const * GIT_COMMIT_SHA = "";
De https://Gist.github.com/larytet/898ec8814dd6b3ceee65532a9916d406
Vous pouvez voir comment je l'ai fait pour memcached dans le commit d'origine .
Fondamentalement, étiquetez occasionnellement et assurez-vous que la chose que vous livrez provient de make dist
ou similaire.