1# -*- coding: utf-8 -*- 2# Copyright 2010-2018, Google Inc. 3# All rights reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions are 7# met: 8# 9# * Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# * Redistributions in binary form must reproduce the above 12# copyright notice, this list of conditions and the following disclaimer 13# in the documentation and/or other materials provided with the 14# distribution. 15# * Neither the name of Google Inc. nor the names of its 16# contributors may be used to endorse or promote products derived from 17# this software without specific prior written permission. 18# 19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31"""A library to operate version definition file. 32 33This script has two functionarity which relate to version definition file. 34 351. Generate version definition file from template and given parameters. 36 To generate version definition file, use GenerateVersionFileFromTemplate 37 method. 38 392. Parse (generated) version definition file. 40 To parse, use MozcVersion class. 41 42Typically version definition file is ${PROJECT_ROOT}/mozc_version.txt 43(Not in the repository because it is generated by this script) 44Typically version template file is 45${PROJECT_ROOT}/data/version/mozc_version_template.bzl, 46which is in the repository. 47The syntax of template is written in the template file. 48""" 49# TODO(matsuzaki): MozcVersion class should have factory method which takes 50# file path and we should remove all the module methods instead to 51# simplify the design. Currently I'd keep this design to reduce 52# client side's change. 53 54import datetime 55import logging 56import optparse 57import os 58import re 59import sys 60 61 62TARGET_PLATFORM_TO_DIGIT = { 63 'Windows': '0', 64 'Mac': '1', 65 'Linux': '2', 66 'Android': '3', 67 'NaCl': '4', 68 } 69 70VERSION_PROPERTIES = [ 71 'MAJOR', 72 'MINOR', 73 'BUILD', 74 'REVISION', 75 'ANDROID_VERSION_CODE', 76 'TARGET_PLATFORM', 77 'QT_VERSION', 78 'ANDROID_APPLICATION_ID', 79 'ANDROID_SERVICE_NAME', 80 'ANDROID_ARCH', 81 'ENGINE_VERSION', 82 # 'DATA_VERSION' is not included as it's the property of data file to be 83 # independent of executables. 84 ] 85 86MOZC_EPOCH = datetime.date(2009, 5, 24) 87 88 89def _GetRevisionForPlatform(revision, target_platform): 90 """Returns the revision for the current platform.""" 91 if revision is None: 92 logging.critical('REVISION property is not found in the template file') 93 sys.exit(1) 94 last_digit = TARGET_PLATFORM_TO_DIGIT.get(target_platform, None) 95 if last_digit is None: 96 logging.critical('target_platform %s is invalid. Accetable ones are %s', 97 target_platform, list(TARGET_PLATFORM_TO_DIGIT.keys())) 98 sys.exit(1) 99 100 if not revision: 101 return revision 102 103 if last_digit: 104 return revision[0:-1] + last_digit 105 106 # If not supported, just use the specified version. 107 return revision 108 109 110def _ParseVersionTemplateFile(template_path, target_platform, 111 android_application_id, android_arch, 112 qt_version): 113 """Parses a version definition file. 114 115 Args: 116 template_path: A filename which has the version definition. 117 target_platform: The target platform on which the programs run. 118 android_application_id: Android application id. 119 android_arch: Android architecture (arm, x86, mips) 120 qt_version: '4' for Qt4, '5' for Qt5, and '' or None for no-Qt. 121 Returns: 122 A dictionary generated from the template file. 123 """ 124 template_dict = {} 125 with open(template_path) as template_file: 126 for line in template_file: 127 matchobj = re.match(r'(\w+)=(.*)', line.strip()) 128 if matchobj: 129 var = matchobj.group(1) 130 val = matchobj.group(2) 131 if var in template_dict: 132 logging.warning(('Dupulicate key: "%s". Later definition "%s"' 133 'overrides earlier one "%s".'), 134 var, val, template_dict[var]) 135 template_dict[var] = val 136 137 # Some properties need to be tweaked. 138 template_dict['REVISION'] = _GetRevisionForPlatform( 139 template_dict.get('REVISION', None), target_platform) 140 141 template_dict['ANDROID_VERSION_CODE'] = ( 142 str(_GetAndroidVersionCode(int(template_dict['BUILD']), android_arch))) 143 144 template_dict['TARGET_PLATFORM'] = target_platform 145 template_dict['QT_VERSION'] = qt_version 146 template_dict['ANDROID_APPLICATION_ID'] = android_application_id 147 template_dict['ANDROID_SERVICE_NAME'] = ( 148 'org.mozc.android.inputmethod.japanese.MozcService') 149 template_dict['ANDROID_ARCH'] = android_arch 150 return template_dict 151 152 153def _GetAndroidVersionCode(base_version_code, arch): 154 """Gets version code based on base version code and architecture. 155 156 Args: 157 base_version_code: is typically equal to the field BUILD in mozc_version.txt 158 arch: Android's architecture (e.g., x86, arm, mips) 159 160 Returns: 161 version code (int) 162 163 Raises: 164 RuntimeError: arch is unexpected one or base_version_code is too big. 165 166 Version code format: 167 0006BBBBBA 168 A: ABI (0: Fat, 6: x86_64, 5:arm64, 4:mips64, 3: x86, 2: armeabi-v7a, 1:mips) 169 B: ANDROID_VERSION_CODE 170 171 Note: 172 - Prefix 6 is introduced because of historical reason. 173 Previously ANDROID_VERSION_CODE (B) was placed after ABI (A) but 174 it's found that swpping the order is reasonable. 175 Previously version code for x86 was always greater than that for armeabi. 176 Therefore version-check rule like "Version code of update must be greater 177 than that of previous" cannot be introduced. 178 """ 179 arch_to_abi_code = { 180 'x86_64': 6, 181 'arm64': 5, 182 'mips64': 4, 183 'x86': 3, 184 'arm': 2, 185 'mips': 1, 186 } 187 abi_code = arch_to_abi_code.get(arch) 188 if abi_code is None: 189 raise RuntimeError('Unexpected architecture; %s' % arch) 190 if base_version_code >= 10000: 191 raise RuntimeError('Version code is greater than 10000. ' 192 'It is time to revisit version code scheme.') 193 return int('6%05d%d' % (base_version_code, abi_code)) 194 195 196def _GetVersionInFormat(properties, version_format): 197 """Returns the version string based on the specified format. 198 199 format can contains @MAJOR@, @MINOR@, @BUILD@ and @REVISION@ which are 200 replaced by self._major, self._minor, self._build, and self._revision 201 respectively. 202 203 Args: 204 properties: a property dicitonary. Typically gotten from 205 _ParseVersionTemplateFile method. 206 version_format: a string which contains version patterns. 207 208 Returns: 209 Return the version string in the format of format. 210 """ 211 212 result = version_format 213 for keyword in VERSION_PROPERTIES: 214 result = result.replace('@%s@' % keyword, properties.get(keyword, '')) 215 return result 216 217 218def GenerateVersionFileFromTemplate(template_path, 219 output_path, 220 version_format, 221 target_platform, 222 android_application_id='', 223 android_arch='arm', 224 qt_version=''): 225 """Generates version file from template file and given parameters. 226 227 Args: 228 template_path: A path to template file. 229 output_path: A path to generated version file. 230 If already exists and the content will not be updated, nothing is done 231 (the timestamp is not updated). 232 version_format: A string which contans version patterns. 233 target_platform: The target platform on which the programs run. 234 android_application_id: Android application id. 235 android_arch: Android architecture (arm, x86, mips) 236 qt_version: '4' for Qt4, '5' for Qt5, and '' or None for no-Qt. 237 """ 238 239 properties = _ParseVersionTemplateFile(template_path, target_platform, 240 android_application_id, 241 android_arch, qt_version) 242 version_definition = _GetVersionInFormat(properties, version_format) 243 old_content = '' 244 if os.path.exists(output_path): 245 # If the target file already exists, need to check the necessity of update 246 # to reduce file-creation frequency. 247 # Currently generated version file is not seen from Make (and Make like 248 # tools) so recreation will not cause serious issue but just in case. 249 with open(output_path) as output_file: 250 old_content = output_file.read() 251 252 if version_definition != old_content: 253 with open(output_path, 'w') as output_file: 254 output_file.write(version_definition) 255 256 257def GenerateVersionFile(version_template_path, version_path, target_platform, 258 android_application_id, android_arch, qt_version): 259 """Reads the version template file and stores it into version_path. 260 261 This doesn't update the "version_path" if nothing will be changed to 262 reduce unnecessary build caused by file timestamp. 263 264 Args: 265 version_template_path: a file name which contains the template of version. 266 version_path: a file name to be stored the official version. 267 target_platform: target platform name. c.f. --target_platform option 268 android_application_id: [Android Only] application id 269 (e.g. org.mozc.android). 270 android_arch: Android architecture (arm, x86, mips) 271 qt_version: '4' for Qt4, '5' for Qt5, and '' or None for no-Qt. 272 """ 273 version_format = '\n'.join([ 274 'MAJOR=@MAJOR@', 275 'MINOR=@MINOR@', 276 'BUILD=@BUILD@', 277 'REVISION=@REVISION@', 278 'ANDROID_VERSION_CODE=@ANDROID_VERSION_CODE@', 279 'TARGET_PLATFORM=@TARGET_PLATFORM@', 280 'QT_VERSION=@QT_VERSION@', 281 'ANDROID_APPLICATION_ID=@ANDROID_APPLICATION_ID@', 282 'ANDROID_SERVICE_NAME=@ANDROID_SERVICE_NAME@', 283 'ANDROID_ARCH=@ANDROID_ARCH@', 284 'ENGINE_VERSION=@ENGINE_VERSION@', 285 ]) + '\n' 286 GenerateVersionFileFromTemplate( 287 version_template_path, 288 version_path, 289 version_format, 290 target_platform=target_platform, 291 android_application_id=android_application_id, 292 android_arch=android_arch, 293 qt_version=qt_version) 294 295 296class MozcVersion(object): 297 """A class to parse and maintain the version definition data. 298 299 Note that this class is not intended to parse "template" file but to 300 "generated" file. 301 Typical usage is; 302 GenerateVersionFileFromTemplate(template_path, version_path, format) 303 version = MozcVersion(version_path) 304 """ 305 306 def __init__(self, path): 307 """Parses a version definition file. 308 309 Args: 310 path: A filename which has the version definition. 311 If the file is not existent, empty properties are prepared instead. 312 """ 313 314 self._properties = {} 315 if not os.path.isfile(path): 316 return 317 with open(path) as file: 318 for line in file: 319 matchobj = re.match(r'(\w+)=(.*)', line.strip()) 320 if matchobj: 321 var = matchobj.group(1) 322 val = matchobj.group(2) 323 if var not in self._properties: 324 self._properties[var] = val 325 326 # Check mandatory properties. 327 for key in VERSION_PROPERTIES: 328 if key not in self._properties: 329 # Don't raise error nor exit. 330 # Error handling is the client's responsibility. 331 logging.warning('Mandatory key "%s" does not exist in %s', key, path) 332 333 def IsDevChannel(self): 334 """Returns true if the parsed version is dev-channel.""" 335 revision = self._properties['REVISION'] 336 return revision is not None and len(revision) >= 3 and revision[-3] == '1' 337 338 def GetTargetPlatform(self): 339 """Returns the target platform. 340 341 Returns: 342 A string for target platform. 343 If the version file is not existent, None is returned. 344 """ 345 return self._properties.get('TARGET_PLATFORM', None) 346 347 def GetQtVersion(self): 348 """Returns the target Qt version. 349 350 Returns: 351 A string that indicates the Qt version. 352 '4' for Qt4, '5' for Qt5, and '' for no-Qt. 353 """ 354 return self._properties.get('QT_VERSION', None) 355 356 def GetVersionString(self): 357 """Returns the normal version info string. 358 359 Returns: 360 a string in format of "MAJOR.MINOR.BUILD.REVISION" 361 """ 362 return self.GetVersionInFormat('@MAJOR@.@MINOR@.@BUILD@.@REVISION@') 363 364 def GetVersionInFormat(self, version_format): 365 """Returns the version string based on the specified format.""" 366 return _GetVersionInFormat(self._properties, version_format) 367 368 def GetAndroidArch(self): 369 """Returns Android architecture.""" 370 return self._properties.get('ANDROID_ARCH', None) 371 372 373def main(): 374 """Generates version file based on the default format. 375 376 Generated file is mozc_version.txt compatible. 377 """ 378 parser = optparse.OptionParser(usage='Usage: %prog ') 379 parser.add_option('--template_path', dest='template_path', 380 help='Path to a template version file.') 381 parser.add_option('--output', dest='output', 382 help='Path to the output version file.') 383 parser.add_option('--target_platform', dest='target_platform', 384 help='Target platform of the version info.') 385 parser.add_option('--android_application_id', dest='android_application_id', 386 default='my.application.id', 387 help='Specifies the application id (Android Only).') 388 parser.add_option('--android_arch', dest='android_arch', 389 default='arm', 390 help='Specifies Android architecture (arm, x86, mips) ' 391 '(Android Only)') 392 parser.add_option('--qtver', dest='qtver', choices=('4', '5', ''), 393 default='', help='Specifies Qt version (desktop only)') 394 (options, args) = parser.parse_args() 395 assert not args, 'Unexpected arguments.' 396 assert options.template_path, 'No --template_path was specified.' 397 assert options.output, 'No --output was specified.' 398 assert options.target_platform, 'No --target_platform was specified.' 399 400 GenerateVersionFile( 401 version_template_path=options.template_path, 402 version_path=options.output, 403 target_platform=options.target_platform, 404 android_application_id=options.android_application_id, 405 android_arch=options.android_arch, 406 qt_version=options.qtver) 407 408if __name__ == '__main__': 409 main() 410