| # -*- coding: utf-8 -*- |
| # Copyright 2010-2015, Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """A library to operate version definition file. |
| |
| This script has two functionarity which relate to version definition file. |
| |
| 1. Generate version definition file from template and given parameters. |
| To generate version definition file, use GenerateVersionFileFromTemplate |
| method. |
| |
| 2. Parse (generated) version definition file. |
| To parse, use MozcVersion class. |
| |
| Typically version definition file is ${PROJECT_ROOT}/mozc_version.txt |
| (Not in the repository because it is generated by this script) |
| Typically version template file is ${PROJECT_ROOT}/mozc_version_template.txt, |
| which is in the repository. |
| The syntax of template is written in the template file. |
| """ |
| # TODO(matsuzaki): MozcVersion class should have factory method which takes |
| # file path and we should remove all the module methods instead to |
| # simplify the design. Currently I'd keep this design to reduce |
| # client side's change. |
| |
| import datetime |
| import logging |
| import optparse |
| import os |
| import re |
| import sys |
| |
| |
| TARGET_PLATFORM_TO_DIGIT = { |
| 'Windows': '0', |
| 'Mac': '1', |
| 'Linux': '2', |
| 'Android': '3', |
| 'NaCl': '4', |
| } |
| |
| VERSION_PROPERTIES = [ |
| 'MAJOR', |
| 'MINOR', |
| 'BUILD', |
| 'REVISION', |
| 'ANDROID_VERSION_CODE', |
| 'FLAG', |
| 'TARGET_PLATFORM', |
| 'ANDROID_APPLICATION_ID', |
| 'ANDROID_SERVICE_NAME', |
| 'NACL_DICTIONARY_VERSION', |
| 'ANDROID_ARCH', |
| ] |
| |
| MOZC_EPOCH = datetime.date(2009, 5, 24) |
| |
| |
| def _GetRevisionForPlatform(revision, target_platform): |
| """Returns the revision for the current platform.""" |
| if revision is None: |
| logging.critical('REVISION property is not found in the template file') |
| sys.exit(1) |
| last_digit = TARGET_PLATFORM_TO_DIGIT.get(target_platform, None) |
| if last_digit is None: |
| logging.critical('target_platform %s is invalid. Accetable ones are %s', |
| target_platform, TARGET_PLATFORM_TO_DIGIT.keys()) |
| sys.exit(1) |
| |
| if not revision: |
| return revision |
| |
| if last_digit: |
| return revision[0:-1] + last_digit |
| |
| # If not supported, just use the specified version. |
| return revision |
| |
| |
| def _ParseVersionTemplateFile(template_path, target_platform, |
| android_application_id, android_arch): |
| """Parses a version definition file. |
| |
| Args: |
| template_path: A filename which has the version definition. |
| target_platform: The target platform on which the programs run. |
| android_application_id: Android application id. |
| android_arch: Android architecture (arm, x86, mips) |
| Returns: |
| A dictionary generated from the template file. |
| """ |
| template_dict = {} |
| with open(template_path) as template_file: |
| for line in template_file: |
| matchobj = re.match(r'(\w+)=(.*)', line.strip()) |
| if matchobj: |
| var = matchobj.group(1) |
| val = matchobj.group(2) |
| if var in template_dict: |
| logging.warning(('Dupulicate key: "%s". Later definition "%s"' |
| 'overrides earlier one "%s".'), |
| var, val, template_dict[var]) |
| template_dict[var] = val |
| |
| # Some properties need to be tweaked. |
| template_dict['REVISION'] = _GetRevisionForPlatform( |
| template_dict.get('REVISION', None), target_platform) |
| num_of_days = datetime.date.today().toordinal() - MOZC_EPOCH.toordinal() |
| if template_dict['BUILD'] == 'daily': |
| template_dict['BUILD'] = str(num_of_days) |
| template_dict.setdefault('FLAG', 'CONTINUOUS') |
| else: |
| template_dict.setdefault('FLAG', 'RELEASE') |
| |
| template_dict['ANDROID_VERSION_CODE'] = ( |
| str(_GetAndroidVersionCode(int(template_dict['BUILD']), android_arch))) |
| |
| template_dict['TARGET_PLATFORM'] = target_platform |
| template_dict['ANDROID_APPLICATION_ID'] = android_application_id |
| template_dict['ANDROID_SERVICE_NAME'] = ( |
| 'org.mozc.android.inputmethod.japanese.MozcService') |
| template_dict['ANDROID_ARCH'] = android_arch |
| return template_dict |
| |
| |
| def _GetAndroidVersionCode(base_version_code, arch): |
| """Gets version code based on base version code and architecture. |
| |
| Args: |
| base_version_code: is typically equal to the field BUILD in mozc_version.txt |
| arch: Android's architecture (e.g., x86, arm, mips) |
| |
| Returns: |
| version code (int) |
| |
| Raises: |
| RuntimeError: arch is unexpected one or base_version_code is too big. |
| |
| Version code format: |
| 0005BBBBBA |
| A: ABI (0: Fat, 6: x86_64, 5:arm64, 4:mips64, 3: x86, 2: armeabi-v7a, 1:mips) |
| B: ANDROID_VERSION_CODE |
| |
| Note: |
| - Prefix 5 is introduced because of historical reason. |
| Previously ANDROID_VERSION_CODE (B) was placed after ABI (A) but |
| it's found that swpping the order is reasonable. |
| Previously version code for x86 was always greater than that for armeabi. |
| Therefore version-check rule like "Version code of update must be greater |
| than that of previous" cannot be introduced. |
| """ |
| arch_to_abi_code = { |
| 'x86_64': 6, |
| 'arm64': 5, |
| 'mips64': 4, |
| 'x86': 3, |
| 'arm': 2, |
| 'mips': 1, |
| } |
| abi_code = arch_to_abi_code.get(arch) |
| if abi_code is None: |
| raise RuntimeError('Unexpected architecture; %s' % arch) |
| if base_version_code >= 10000: |
| raise RuntimeError('Version code is greater than 10000. ' |
| 'It is time to revisit version code scheme.') |
| return int('5%05d%d' % (base_version_code, abi_code)) |
| |
| |
| def _GetVersionInFormat(properties, version_format): |
| """Returns the version string based on the specified format. |
| |
| format can contains @MAJOR@, @MINOR@, @BUILD@ and @REVISION@ which are |
| replaced by self._major, self._minor, self._build, and self._revision |
| respectively. |
| |
| Args: |
| properties: a property dicitonary. Typically gotten from |
| _ParseVersionTemplateFile method. |
| version_format: a string which contains version patterns. |
| |
| Returns: |
| Return the version string in the format of format. |
| """ |
| |
| result = version_format |
| for keyword in VERSION_PROPERTIES: |
| result = result.replace('@%s@' % keyword, properties.get(keyword, '')) |
| return result |
| |
| |
| def GenerateVersionFileFromTemplate(template_path, |
| output_path, |
| version_format, |
| target_platform, |
| android_application_id='', |
| android_arch='arm'): |
| """Generates version file from template file and given parameters. |
| |
| Args: |
| template_path: A path to template file. |
| output_path: A path to generated version file. |
| If already exists and the content will not be updated, nothing is done |
| (the timestamp is not updated). |
| version_format: A string which contans version patterns. |
| target_platform: The target platform on which the programs run. |
| android_application_id: Android application id. |
| android_arch: Android architecture (arm, x86, mips) |
| """ |
| |
| properties = _ParseVersionTemplateFile(template_path, target_platform, |
| android_application_id, |
| android_arch) |
| version_definition = _GetVersionInFormat(properties, version_format) |
| old_content = '' |
| if os.path.exists(output_path): |
| # If the target file already exists, need to check the necessity of update |
| # to reduce file-creation frequency. |
| # Currently generated version file is not seen from Make (and Make like |
| # tools) so recreation will not cause serious issue but just in case. |
| with open(output_path) as output_file: |
| old_content = output_file.read() |
| |
| if version_definition != old_content: |
| with open(output_path, 'w') as output_file: |
| output_file.write(version_definition) |
| |
| |
| def GenerateVersionFile(version_template_path, version_path, target_platform, |
| android_application_id, android_arch): |
| """Reads the version template file and stores it into version_path. |
| |
| This doesn't update the "version_path" if nothing will be changed to |
| reduce unnecessary build caused by file timestamp. |
| |
| Args: |
| version_template_path: a file name which contains the template of version. |
| version_path: a file name to be stored the official version. |
| target_platform: target platform name. c.f. --target_platform option |
| android_application_id: [Android Only] application id |
| (e.g. org.mozc.android). |
| android_arch: Android architecture (arm, x86, mips) |
| """ |
| version_format = '\n'.join([ |
| 'MAJOR=@MAJOR@', |
| 'MINOR=@MINOR@', |
| 'BUILD=@BUILD@', |
| 'REVISION=@REVISION@', |
| 'ANDROID_VERSION_CODE=@ANDROID_VERSION_CODE@', |
| 'FLAG=@FLAG@', |
| 'TARGET_PLATFORM=@TARGET_PLATFORM@', |
| 'ANDROID_APPLICATION_ID=@ANDROID_APPLICATION_ID@', |
| 'ANDROID_SERVICE_NAME=@ANDROID_SERVICE_NAME@', |
| 'NACL_DICTIONARY_VERSION=@NACL_DICTIONARY_VERSION@', |
| 'ANDROID_ARCH=@ANDROID_ARCH@' |
| ]) + '\n' |
| GenerateVersionFileFromTemplate( |
| version_template_path, |
| version_path, |
| version_format, |
| target_platform=target_platform, |
| android_application_id=android_application_id, |
| android_arch=android_arch) |
| |
| |
| class MozcVersion(object): |
| """A class to parse and maintain the version definition data. |
| |
| Note that this class is not intended to parse "template" file but to |
| "generated" file. |
| Typical usage is; |
| GenerateVersionFileFromTemplate(template_path, version_path, format) |
| version = MozcVersion(version_path) |
| """ |
| |
| def __init__(self, path): |
| """Parses a version definition file. |
| |
| Args: |
| path: A filename which has the version definition. |
| If the file is not existent, empty properties are prepared instead. |
| """ |
| |
| self._properties = {} |
| if not os.path.isfile(path): |
| return |
| for line in open(path): |
| matchobj = re.match(r'(\w+)=(.*)', line.strip()) |
| if matchobj: |
| var = matchobj.group(1) |
| val = matchobj.group(2) |
| if var not in self._properties: |
| self._properties[var] = val |
| |
| # Check mandatory properties. |
| for key in VERSION_PROPERTIES: |
| if key not in self._properties: |
| # Don't raise error nor exit. |
| # Error handling is the client's responsibility. |
| logging.warning('Mandatory key "%s" does not exist in %s', key, path) |
| |
| def IsDevChannel(self): |
| """Returns true if the parsed version is dev-channel.""" |
| revision = self._properties['REVISION'] |
| return revision is not None and len(revision) >= 3 and revision[-3] == '1' |
| |
| def GetTargetPlatform(self): |
| """Returns the target platform. |
| |
| Returns: |
| A string for target platform. |
| If the version file is not existent, None is returned. |
| """ |
| return self._properties.get('TARGET_PLATFORM', None) |
| |
| def GetVersionString(self): |
| """Returns the normal version info string. |
| |
| Returns: |
| a string in format of "MAJOR.MINOR.BUILD.REVISION" |
| """ |
| return self.GetVersionInFormat('@MAJOR@.@MINOR@.@BUILD@.@REVISION@') |
| |
| def GetVersionInFormat(self, version_format): |
| """Returns the version string based on the specified format.""" |
| return _GetVersionInFormat(self._properties, version_format) |
| |
| def GetAndroidArch(self): |
| """Returns Android architecture.""" |
| return self._properties.get('ANDROID_ARCH', None) |
| |
| |
| def main(): |
| """Generates version file based on the default format. |
| |
| Generated file is mozc_version.txt compatible. |
| """ |
| parser = optparse.OptionParser(usage='Usage: %prog ') |
| parser.add_option('--template_path', dest='template_path', |
| help='Path to a template version file.') |
| parser.add_option('--output', dest='output', |
| help='Path to the output version file.') |
| parser.add_option('--target_platform', dest='target_platform', |
| help='Target platform of the version info.') |
| parser.add_option('--android_application_id', dest='android_application_id', |
| default='my.application.id', |
| help='Specifies the application id (Android Only).') |
| parser.add_option('--android_arch', dest='android_arch', |
| default='arm', |
| help='Specifies Android architecture (arm, x86, mips) ' |
| '(Android Only)') |
| (options, args) = parser.parse_args() |
| assert not args, 'Unexpected arguments.' |
| assert options.template_path, 'No --template_path was specified.' |
| assert options.output, 'No --output was specified.' |
| assert options.target_platform, 'No --target_platform was specified.' |
| |
| GenerateVersionFile( |
| version_template_path=options.template_path, |
| version_path=options.output, |
| target_platform=options.target_platform, |
| android_application_id=options.android_application_id, |
| android_arch=options.android_arch) |
| |
| if __name__ == '__main__': |
| main() |