mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-11-30 08:24:11 +01:00
300 lines
9.7 KiB
Python
300 lines
9.7 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Utility functions to access git repository info and
|
||
|
# generate source files and binary objects using templates.
|
||
|
#
|
||
|
# (c) 2011, The OpenPilot Team, http://www.openpilot.org
|
||
|
# See also: The GNU Public License (GPL) Version 3
|
||
|
#
|
||
|
|
||
|
from subprocess import Popen, PIPE
|
||
|
from re import search, MULTILINE
|
||
|
from datetime import datetime
|
||
|
from string import Template
|
||
|
import argparse
|
||
|
import hashlib
|
||
|
import sys
|
||
|
|
||
|
class Repo:
|
||
|
"""A simple git repository HEAD commit info class
|
||
|
|
||
|
This simple class provides object notation to access
|
||
|
the git repository current commit info. If one needs
|
||
|
better access one can try the GitPython class available
|
||
|
here:
|
||
|
http://packages.python.org/GitPython
|
||
|
It is not installed by default, so we cannot rely on it.
|
||
|
|
||
|
Example:
|
||
|
r = Repo('/path/to/git/repository')
|
||
|
print "path: ", r.path()
|
||
|
print "origin: ", r.origin()
|
||
|
print "hash: ", r.hash()
|
||
|
print "short hash: ", r.hash(8)
|
||
|
print "Unix time: ", r.time()
|
||
|
print "commit date:", r.time("%Y%m%d")
|
||
|
print "commit tag: ", r.tag()
|
||
|
print "branch: ", r.branch()
|
||
|
print "release tag:", r.reltag()
|
||
|
"""
|
||
|
|
||
|
def _exec(self, cmd):
|
||
|
"""Execute git using cmd as arguments"""
|
||
|
self._git = 'git'
|
||
|
git = Popen(self._git + " " + cmd, cwd=self._path,
|
||
|
shell=True, stdout=PIPE, stderr=PIPE)
|
||
|
self._out, self._err = git.communicate()
|
||
|
self._rc = git.poll()
|
||
|
|
||
|
def _get_origin(self):
|
||
|
"""Get and store the repository origin URL"""
|
||
|
self._origin = None
|
||
|
self._exec('remote -v')
|
||
|
if self._rc == 0:
|
||
|
m = search(r"^origin\s+(.+)\s+\(fetch\)$", self._out, MULTILINE)
|
||
|
if m:
|
||
|
self._origin = m.group(1)
|
||
|
|
||
|
def _get_time(self):
|
||
|
"""Get and store HEAD commit timestamp in Unix format
|
||
|
|
||
|
We use commit timestamp rather than the build time,
|
||
|
so it always is the same for the current commit or tag.
|
||
|
"""
|
||
|
self._time = None
|
||
|
self._exec('log -n1 --no-color --format=format:%ct HEAD')
|
||
|
if self._rc == 0:
|
||
|
self._time = self._out
|
||
|
|
||
|
def _get_tag(self):
|
||
|
"""Get and store git tag for the HEAD commit"""
|
||
|
self._tag = None
|
||
|
self._exec('describe --exact-match HEAD')
|
||
|
if self._rc == 0:
|
||
|
self._tag = self._out.strip(' \t\n\r')
|
||
|
|
||
|
def _get_branch(self):
|
||
|
"""Get and store current branch containing the HEAD commit"""
|
||
|
self._branch = None
|
||
|
self._exec('branch --contains HEAD')
|
||
|
if self._rc == 0:
|
||
|
m = search(r"^\*\s+(.+)$", self._out, MULTILINE)
|
||
|
if m:
|
||
|
self._branch = m.group(1)
|
||
|
|
||
|
def __init__(self, path = "."):
|
||
|
"""Initialize object instance and read repo info"""
|
||
|
self._path = path
|
||
|
self._exec('rev-parse --verify HEAD')
|
||
|
if self._rc == 0:
|
||
|
self._hash = self._out.strip(' \t\n\r')
|
||
|
self._get_origin()
|
||
|
self._get_time()
|
||
|
self._get_tag()
|
||
|
self._get_branch()
|
||
|
else:
|
||
|
self._hash = None
|
||
|
self._origin = None
|
||
|
self._time = None
|
||
|
self._tag = None
|
||
|
self._branch = None
|
||
|
|
||
|
def path(self):
|
||
|
"""Return the repository path"""
|
||
|
return self._path
|
||
|
|
||
|
def origin(self, none = None):
|
||
|
"""Return fetch origin of the repository"""
|
||
|
if self._origin == None:
|
||
|
return none
|
||
|
else:
|
||
|
return self._origin
|
||
|
|
||
|
def hash(self, n = 40, none = None):
|
||
|
"""Return hash of the HEAD commit"""
|
||
|
if self._hash == None:
|
||
|
return none
|
||
|
else:
|
||
|
return self._hash[:n]
|
||
|
|
||
|
def time(self, format = None, none = None):
|
||
|
"""Return Unix or formatted time of the HEAD commit"""
|
||
|
if self._time == None:
|
||
|
return none
|
||
|
else:
|
||
|
if format == None:
|
||
|
return self._time
|
||
|
else:
|
||
|
return datetime.utcfromtimestamp(float(self._time)).strftime(format)
|
||
|
|
||
|
def tag(self, none = None):
|
||
|
"""Return git tag for the HEAD commit or given string if none"""
|
||
|
if self._tag == None:
|
||
|
return none
|
||
|
else:
|
||
|
return self._tag
|
||
|
|
||
|
def branch(self, none = None):
|
||
|
"""Return git branch containing the HEAD or given string if none"""
|
||
|
if self._branch == None:
|
||
|
return none
|
||
|
else:
|
||
|
return self._branch
|
||
|
|
||
|
def info(self):
|
||
|
"""Print some repository info"""
|
||
|
print "path: ", self.path()
|
||
|
print "origin: ", self.origin()
|
||
|
print "Unix time: ", self.time()
|
||
|
print "commit date:", self.time("%Y%m%d")
|
||
|
print "hash: ", self.hash()
|
||
|
print "short hash: ", self.hash(8)
|
||
|
print "branch: ", self.branch()
|
||
|
print "commit tag: ", self.tag()
|
||
|
|
||
|
def file_from_template(tpl_name, out_name, dict):
|
||
|
"""Create or update file from template using dictionary
|
||
|
|
||
|
This function reads the template, performs placeholder replacement
|
||
|
using the dictionary and checks if output file with such content
|
||
|
already exists. If no such file or file data is different from
|
||
|
expected then it will be ovewritten with new data. Otherwise it
|
||
|
will not be updated so make will not update dependent targets.
|
||
|
|
||
|
Example:
|
||
|
# template.c:
|
||
|
# char source[] = "${OUTFILENAME}";
|
||
|
# uint32_t timestamp = ${UNIXTIME};
|
||
|
# uint32_t hash = 0x${HASH8};
|
||
|
|
||
|
r = Repo('/path/to/git/repository')
|
||
|
tpl_name = "template.c"
|
||
|
out_name = "output.c"
|
||
|
|
||
|
dictionary = dict(
|
||
|
HASH8 = r.hash(8),
|
||
|
UNIXTIME = r.time(),
|
||
|
OUTFILENAME = out_name,
|
||
|
)
|
||
|
|
||
|
file_from_template(tpl_name, out_name, dictionary)
|
||
|
"""
|
||
|
|
||
|
# Read template first
|
||
|
tf = open(tpl_name, "rb")
|
||
|
tpl = tf.read()
|
||
|
tf.close()
|
||
|
|
||
|
# Replace placeholders using dictionary
|
||
|
out = Template(tpl).substitute(dict)
|
||
|
|
||
|
# Check if output file already exists
|
||
|
try:
|
||
|
of = open(out_name, "rb")
|
||
|
except IOError:
|
||
|
# No file - create new
|
||
|
of = open(out_name, "wb")
|
||
|
of.write(out)
|
||
|
of.close()
|
||
|
else:
|
||
|
# File exists - overwite only if content is different
|
||
|
inp = of.read()
|
||
|
of.close()
|
||
|
if inp != out:
|
||
|
of = open(out_name, "wb")
|
||
|
of.write(out)
|
||
|
of.close()
|
||
|
|
||
|
def sha1(file):
|
||
|
"""Provides C source representation of sha1 sum of file."""
|
||
|
if file == None:
|
||
|
return ""
|
||
|
else:
|
||
|
sha1 = hashlib.sha1()
|
||
|
with open(file, 'rb') as f:
|
||
|
for chunk in iter(lambda: f.read(8192), ''):
|
||
|
sha1.update(chunk)
|
||
|
hex_stream = lambda s:",".join(['0x'+hex(ord(c))[2:].zfill(2) for c in s])
|
||
|
return hex_stream(sha1.digest())
|
||
|
|
||
|
def main():
|
||
|
"""This utility uses git repository in the current working directory
|
||
|
or from the given path to extract some info about it and HEAD commit.
|
||
|
Then some variables in the form of ${VARIABLE} could be replaced by
|
||
|
collected data. Optional board type, board revision and sha1 sum
|
||
|
of given image file could be applied as well or will be replaced by
|
||
|
empty strings if not defined.
|
||
|
|
||
|
If --info option is given, some repository info will be printed to
|
||
|
stdout.
|
||
|
|
||
|
If --format option is given then utility prints the format string
|
||
|
after substitution to the standard output.
|
||
|
|
||
|
If --outfile option is given then the --template option should be
|
||
|
defined too. In that case the utility reads a template file, performs
|
||
|
variable substitution and writes the result into output file. Output
|
||
|
file will be overwritten only if its content differs from expected.
|
||
|
Otherwise it will not be touched, so make utility will not remake
|
||
|
dependent targets.
|
||
|
"""
|
||
|
|
||
|
# Parse command line.
|
||
|
parser = argparse.ArgumentParser(
|
||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
|
description = "Performs variable substitution in template file or string.",
|
||
|
epilog = main.__doc__);
|
||
|
|
||
|
parser.add_argument('--path', default='.',
|
||
|
help='path to the git repository');
|
||
|
parser.add_argument('--info', action='store_true',
|
||
|
help='print repository info to stdout');
|
||
|
parser.add_argument('--format',
|
||
|
help='format string to print to stdout');
|
||
|
parser.add_argument('--template',
|
||
|
help='name of template file');
|
||
|
parser.add_argument('--outfile',
|
||
|
help='name of output file');
|
||
|
parser.add_argument('--image',
|
||
|
help='name of image file for sha1 calculation');
|
||
|
parser.add_argument('--type', default="",
|
||
|
help='board type, for example, 0x04 for CopterControl');
|
||
|
parser.add_argument('--revision', default = "",
|
||
|
help='board revision, for example, 0x01');
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
# Process arguments. No advanced error handling is here.
|
||
|
# Any error will raise an exception and terminate process
|
||
|
# with non-zero exit code.
|
||
|
r = Repo(args.path)
|
||
|
|
||
|
dictionary = dict(
|
||
|
TEMPLATE = args.template,
|
||
|
OUTFILENAME = args.outfile,
|
||
|
ORIGIN = r.origin(),
|
||
|
HASH = r.hash(),
|
||
|
HASH8 = r.hash(8),
|
||
|
TAG_OR_BRANCH = r.tag(r.branch('unreleased')),
|
||
|
TAG_OR_HASH8 = r.tag(r.hash(8, 'untagged')),
|
||
|
UNIXTIME = r.time(),
|
||
|
DATE = r.time('%Y%m%d'),
|
||
|
BOARD_TYPE = args.type,
|
||
|
BOARD_REVISION = args.revision,
|
||
|
SHA1 = sha1(args.image),
|
||
|
)
|
||
|
|
||
|
if args.info:
|
||
|
r.info()
|
||
|
|
||
|
if args.format != None:
|
||
|
print Template(args.format).substitute(dictionary)
|
||
|
|
||
|
if args.outfile != None:
|
||
|
file_from_template(args.template, args.outfile, dictionary)
|
||
|
|
||
|
return 0
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
sys.exit(main())
|