1#!/usr/bin/env python 2# Generate version information for a program 3# 4# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net> 5# 6# This file may be distributed under the terms of the GNU GPLv3 license. 7import sys, os, subprocess, shlex, time, socket, optparse, logging, traceback 8 9VERSION_FORMAT = """ 10/* DO NOT EDIT! This is an autogenerated file. See scripts/buildversion.py. */ 11#define BUILD_VERSION "%s" 12#define BUILD_TOOLS "%s" 13""" 14 15# Run program and return the specified output 16def check_output(prog): 17 logging.debug("Running %s" % (repr(prog),)) 18 try: 19 process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE) 20 output = process.communicate()[0] 21 retcode = process.poll() 22 except OSError: 23 logging.debug("Exception on run: %s" % (traceback.format_exc(),)) 24 return "" 25 logging.debug("Got (code=%s): %s" % (retcode, repr(output))) 26 if retcode: 27 return "" 28 try: 29 return output.decode() 30 except UnicodeError: 31 logging.debug("Exception on decode: %s" % (traceback.format_exc(),)) 32 return "" 33 34# Obtain version info from "git" program 35def git_version(): 36 if not os.path.exists('.git'): 37 logging.debug("No '.git' file/directory found") 38 return "" 39 ver = check_output("git describe --always --tags --long --dirty").strip() 40 logging.debug("Got git version: %s" % (repr(ver),)) 41 return ver 42 43# Look for version in a ".version" file. Official release tarballs 44# have this file (see scripts/tarball.sh). 45def file_version(): 46 if not os.path.isfile('.version'): 47 logging.debug("No '.version' file found") 48 return "" 49 try: 50 f = open('.version', 'r') 51 ver = f.readline().strip() 52 f.close() 53 except OSError: 54 logging.debug("Exception on read: %s" % (traceback.format_exc(),)) 55 return "" 56 logging.debug("Got .version: %s" % (repr(ver),)) 57 return ver 58 59# Generate an output file with the version information 60def write_version(outfile, version, toolstr): 61 logging.debug("Write file %s and %s" % (repr(version), repr(toolstr))) 62 sys.stdout.write("Version: %s\n" % (version,)) 63 f = open(outfile, 'w') 64 f.write(VERSION_FORMAT % (version, toolstr)) 65 f.close() 66 67# Run "tool --version" for each specified tool and extract versions 68def tool_versions(tools): 69 tools = [t.strip() for t in tools.split(';')] 70 versions = ['', ''] 71 success = 0 72 for tool in tools: 73 # Extract first line from "tool --version" output 74 verstr = check_output("%s --version" % (tool,)).split('\n')[0] 75 # Check if this tool looks like a binutils program 76 isbinutils = 0 77 if verstr.startswith('GNU '): 78 isbinutils = 1 79 verstr = verstr[4:] 80 # Extract version information and exclude program name 81 if ' ' not in verstr: 82 continue 83 prog, ver = verstr.split(' ', 1) 84 if not prog or not ver: 85 continue 86 # Check for any version conflicts 87 if versions[isbinutils] and versions[isbinutils] != ver: 88 logging.debug("Mixed version %s vs %s" % ( 89 repr(versions[isbinutils]), repr(ver))) 90 versions[isbinutils] = "mixed" 91 continue 92 versions[isbinutils] = ver 93 success += 1 94 cleanbuild = versions[0] and versions[1] and success == len(tools) 95 return cleanbuild, "gcc: %s binutils: %s" % (versions[0], versions[1]) 96 97def main(): 98 usage = "%prog [options] <outputheader.h>" 99 opts = optparse.OptionParser(usage) 100 opts.add_option("-e", "--extra", dest="extra", default="", 101 help="extra version string to append to version") 102 opts.add_option("-t", "--tools", dest="tools", default="", 103 help="list of build programs to extract version from") 104 opts.add_option("-v", action="store_true", dest="verbose", 105 help="enable debug messages") 106 107 options, args = opts.parse_args() 108 if len(args) != 1: 109 opts.error("Incorrect arguments") 110 outfile = args[0] 111 if options.verbose: 112 logging.basicConfig(level=logging.DEBUG) 113 114 cleanbuild, toolstr = tool_versions(options.tools) 115 116 ver = git_version() 117 cleanbuild = cleanbuild and 'dirty' not in ver 118 if not ver: 119 ver = file_version() 120 # We expect the "extra version" to contain information on the 121 # distributor and distribution package version (if 122 # applicable). It is a "clean" build if this is a build from 123 # an official release tarball and the above info is present. 124 cleanbuild = cleanbuild and ver and options.extra != "" 125 if not ver: 126 ver = "?" 127 if not cleanbuild: 128 btime = time.strftime("%Y%m%d_%H%M%S") 129 hostname = socket.gethostname() 130 ver = "%s-%s-%s" % (ver, btime, hostname) 131 write_version(outfile, ver + options.extra, toolstr) 132 133if __name__ == '__main__': 134 main() 135