#!/usr/bin/env python # # Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Usage: merge-event-log-tags.py [-o output_file] [input_files...] Merge together zero or more event-logs-tags files to produce a single output file, stripped of comments. Checks that no tag numbers conflict and fails if they do. -h to display this usage message and exit. """ from __future__ import print_function import getopt try: import hashlib except ImportError: import md5 as hashlib import struct import sys try: from cStringIO import StringIO except ImportError: from io import StringIO import event_log_tags def iteritems(obj): if hasattr(obj, 'iteritems'): return obj.iteritems() return obj.items() errors = [] warnings = [] output_file = None pre_merged_file = None # Tags with a tag number of ? are assigned a tag in the range # [ASSIGN_START, ASSIGN_LIMIT). ASSIGN_START = 900000 ASSIGN_LIMIT = 1000000 try: opts, args = getopt.getopt(sys.argv[1:], "ho:m:") except getopt.GetoptError as err: print(str(err)) print(__doc__) sys.exit(2) for o, a in opts: if o == "-h": print(__doc__) sys.exit(2) elif o == "-o": output_file = a elif o == "-m": pre_merged_file = a else: print("unhandled option %s" % (o,), file=sys.stderr) sys.exit(1) # Restrictions on tags: # # Tag names must be unique. (If the tag number and description are # also the same, a warning is issued instead of an error.) # # Explicit tag numbers must be unique. (If the tag name is also the # same, no error is issued because the above rule will issue a # warning or error.) by_tagname = {} by_tagnum = {} pre_merged_tags = {} if pre_merged_file: for t in event_log_tags.TagFile(pre_merged_file).tags: pre_merged_tags[t.tagname] = t for fn in args: tagfile = event_log_tags.TagFile(fn) for t in tagfile.tags: tagnum = t.tagnum tagname = t.tagname description = t.description if t.tagname in by_tagname: orig = by_tagname[t.tagname] # Allow an explicit tag number to define an implicit tag number if orig.tagnum is None: orig.tagnum = t.tagnum elif t.tagnum is None: t.tagnum = orig.tagnum if (t.tagnum == orig.tagnum and t.description == orig.description): # if the name and description are identical, issue a warning # instead of failing (to make it easier to move tags between # projects without breaking the build). tagfile.AddWarning("tag \"%s\" (%s) duplicated in %s:%d" % (t.tagname, t.tagnum, orig.filename, orig.linenum), linenum=t.linenum) else: tagfile.AddError( "tag name \"%s\" used by conflicting tag %s from %s:%d" % (t.tagname, orig.tagnum, orig.filename, orig.linenum), linenum=t.linenum) continue if t.tagnum is not None and t.tagnum in by_tagnum: orig = by_tagnum[t.tagnum] if t.tagname != orig.tagname: tagfile.AddError( "tag number %d used by conflicting tag \"%s\" from %s:%d" % (t.tagnum, orig.tagname, orig.filename, orig.linenum), linenum=t.linenum) continue by_tagname[t.tagname] = t if t.tagnum is not None: by_tagnum[t.tagnum] = t errors.extend(tagfile.errors) warnings.extend(tagfile.warnings) if errors: for fn, ln, msg in errors: print("%s:%d: error: %s" % (fn, ln, msg), file=sys.stderr) sys.exit(1) if warnings: for fn, ln, msg in warnings: print("%s:%d: warning: %s" % (fn, ln, msg), file=sys.stderr) # Python's hash function (a) isn't great and (b) varies between # versions of python. Using md5 is overkill here but is the same from # platform to platform and speed shouldn't matter in practice. def hashname(str): d = hashlib.md5(str).digest()[:4] return struct.unpack("!I", d)[0] # Assign a tag number to all the entries that say they want one # assigned. We do this based on a hash of the tag name so that the # numbers should stay relatively stable as tags are added. # If we were provided pre-merged tags (w/ the -m option), then don't # ever try to allocate one, just fail if we don't have a number for name, t in sorted(iteritems(by_tagname)): if t.tagnum is None: if pre_merged_tags: try: t.tagnum = pre_merged_tags[t.tagname] except KeyError: print("Error: Tag number not defined for tag `%s'." " Have you done a full build?" % t.tagname, file=sys.stderr) sys.exit(1) else: while True: x = (hashname(name) % (ASSIGN_LIMIT - ASSIGN_START - 1)) + ASSIGN_START if x not in by_tagnum: t.tagnum = x by_tagnum[x] = t break name = "_" + name # by_tagnum should be complete now; we've assigned numbers to all tags. buffer = StringIO() for n, t in sorted(iteritems(by_tagnum)): if t.description: buffer.write("%d %s %s\n" % (t.tagnum, t.tagname, t.description)) else: buffer.write("%d %s\n" % (t.tagnum, t.tagname)) event_log_tags.WriteOutput(output_file, buffer)