diff --git a/gen_version.py b/gen_version.py new file mode 100755 index 000000000..749f12edd --- /dev/null +++ b/gen_version.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# +# Copyright 2015 Google Inc. All rights reserved. +# +# 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. + +"""Generate version string for NotoColorEmoji. + +This parses the color emoji template file and updates the lines +containing version string info, writing a new file. + +The nameID 5 field in the emoji font should reflect the commit/date +of the repo it was built from. This will build a string of the following +format: + Version 1.39;GOOG;noto-emoji:20170220:a8a215d2e889' + +This is intended to indicate that it was built by Google from noto-emoji +at commit a8a215d2e889 and date 20170220 (since dates are a bit easier +to locate in time than commit hashes). + +For building with external data we don't include the commit id as we +might be using different resoruces. Instead the version string is: + Version 1.39;GOOG;noto-emoji:20170518;BETA + +Here the date is the current date, and the message after 'BETA ' is +provided using the '-b' flag. There's no commit hash. This also +bypasses some checks about the state of the repo. + +The relase number should have 2 or 3 minor digits. Right now we've been +using 2 but at the next major relase we probably want to use 3. This +supports both. It will bump the version number if none is provided, +maintaining the minor digit length. +""" + +import argparse +import datetime +import re + +from nototools import tool_utils + +# These are not very lenient, we expect to be applied to the noto color +# emoji template ttx file which matches these. Why then require the +# input argument, you ask? Um... testing? +_nameid_re = re.compile(r'\s*') + +def _get_existing_version(lines): + """Scan lines for all existing version numbers, and ensure they match. + Return the matched version number string.""" + + version = None + def check_version(new_version): + if version is not None and new_version != version: + raise Exception( + 'version %s and namerecord version %s do not match' % ( + version, new_version)) + return new_version + + saw_nameid = False + for line in lines: + if saw_nameid: + saw_nameid = False + m = _version_re.match(line) + if not m: + raise Exception('could not match line "%s" in namerecord' % line) + version = check_version(m.group(1)) + elif _nameid_re.match(line): + saw_nameid = True + else: + m = _headrev_re.match(line) + if m: + version = check_version(m.group(1)) + return version + + +def _version_to_mm(version): + majs, mins = version.split('.') + minor_len = len(mins) + return int(majs), int(mins), minor_len + + +def _mm_to_version(major, minor, minor_len): + fmt = '%%d.%%0%dd' % minor_len + return fmt % (major, minor) + + +def _version_compare(lhs, rhs): + lmaj, lmin, llen = _version_to_mm(lhs) + rmaj, rmin, rlen = _version_to_mm(rhs) + # if major versions differ, we don't care about the minor length, else + # they should be the same + if lmaj != rmaj: + return lmaj - rmaj + if llen != rlen: + raise Exception('minor version lengths differ: "%s" and "%s"' % (lhs, rhs)) + return lmin - rmin + + +def _version_bump(version): + major, minor, minor_len = _version_to_mm(version) + minor = (minor + 1) % (10 ** minor_len) + if minor == 0: + raise Exception('cannot bump version "%s", requires new major' % version) + return _mm_to_version(major, minor, minor_len) + + +def _get_repo_version_str(beta): + """See above for description of this string.""" + if beta is not None: + date_str = datetime.date.today().strftime('%Y%m%d') + return 'GOOG;noto-emoji:%s;BETA %s' % (date_str, beta) + + p = tool_utils.resolve_path('[emoji]') + commit, date, _ = tool_utils.git_head_commit(p) + if not tool_utils.git_check_remote_commit(p, commit): + raise Exception('emoji not on upstream master branch') + date_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})') + m = date_re.match(date) + if not m: + raise Exception('could not match "%s" with "%s"' % (date, date_re.pattern)) + ymd = ''.join(m.groups()) + return 'GOOG;noto-emoji:%s:%s' % (ymd, commit[:12]) + + +def _replace_existing_version(lines, version, version_str): + """Update lines with new version strings in appropriate places.""" + saw_nameid = False + for i in range(len(lines)): + line = lines[i] + if saw_nameid: + saw_nameid = False + # preserve indentation + lead_ws = len(line) - len(line.lstrip()) + lines[i] = line[:lead_ws] + version_str + '\n' + elif _nameid_re.match(line): + saw_nameid = True + elif _headrev_re.match(line): + lead_ws = len(line) - len(line.lstrip()) + lines[i] = line[:lead_ws] + '\n' % version + + +def update_version(srcfile, dstfile, version, beta): + """Update version in srcfile and write to dstfile. If version is None, + bumps the current version, else version must be greater than the + current verison.""" + + with open(srcfile, 'r') as f: + lines = f.readlines() + current_version = _get_existing_version(lines) + if not version: + version = _version_bump(current_version) + elif version and _version_compare(version, current_version) <= 0: + raise Exception('new version %s is <= current version %s' % ( + version, current_version)) + version_str = 'Version %s;%s' % (version, _get_repo_version_str(beta)) + _replace_existing_version(lines, version, version_str) + with open(dstfile, 'w') as f: + for line in lines: + f.write(line) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '-v', '--version', help='version number, default bumps the current ' + 'version', metavar='ver') + parser.add_argument( + '-s', '--src', help='ttx file with name and head tables', + metavar='file', required=True) + parser.add_argument( + '-d', '--dst', help='name of edited ttx file to write', + metavar='file', required=True) + parser.add_argument( + '-b', '--beta', help='beta tag if font is built using external resources') + args = parser.parse_args() + + update_version(args.src, args.dst, args.version, args.beta) + + +if __name__ == '__main__': + main()