1#!/usr/bin/env python3 2# Copyright 2019 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Different build variants of Chrome for Android have different version codes. 6 7For targets that have the same package name (e.g. Chrome, Chrome Modern, 8Monochrome, Trichrome), Play Store considers them the same app and will push the 9supported app with the highest version code to devices. Note that Play Store 10does not support hosting two different apps with same version code and package 11name. 12 13Each version code generated by this script will be used by one or more APKs. 14 15Webview channels must have unique version codes for a couple reasons: 16a) Play Store does not support having the same version code for different 17 versions of a package. Without unique codes, promoting a beta apk to stable 18 would require first removing the beta version. 19b) Firebase project support (used by official builders) requires unique 20 [version code + package name]. 21 We cannot add new webview package names for new channels because webview 22 packages are allowlisted by Android as webview providers. 23 24WEBVIEW_STABLE, WEBVIEW_BETA, WEBVIEW_DEV are all used for standalone webview, 25whereas the others are used for various chrome APKs. 26 27TRICHROME_BETA is used for TrichromeChrome, TrichromeWebView, and 28TrichromeLibrary when these are compiled to use the stable package name. Similar 29to how WEBVIEW_STABLE/WEBVIEW_BETA work, this allows users to opt into the open 30Beta Track for the stable package. When Trichrome is configured to use a 31distinct package name for the Beta package, the version code will use TRICHROME 32instead of TRICHROME_BETA. 33 34Note that a package digit of '3' for Webview is reserved for Trichrome Webview. 35The same versionCode is used for both Trichrome Chrome and Trichrome Webview. 36 37Version code values are constructed like this: 38 39 {full BUILD number}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}. 40 41For example: 42 43 Build 3721, patch 0, ChromeModern (1), on ARM64 (5): 372100015 44 Build 3721, patch 9, Monochrome (2), on ARM (0): 372100920 45 46""" 47 48import argparse 49from collections import namedtuple 50 51# Package name version bits. 52_PACKAGE_NAMES = { 53 'CHROME': 0, 54 'CHROME_MODERN': 10, 55 'MONOCHROME': 20, 56 'TRICHROME': 30, 57 'TRICHROME_BETA': 40, 58 'TRICHROME_AUTO': 50, 59 'TRICHROME_DESKTOP': 60, 60 'WEBVIEW_STABLE': 0, 61 'WEBVIEW_BETA': 10, 62 'WEBVIEW_DEV': 20, 63} 64""" "Next" builds get +500 on their patch number. 65 66This ensures that they are considered "newer" than any non-next build of the 67same branch number; this is a workaround for Android requiring a total ordering 68of versions when we only really have a partial ordering. This assumes that the 69actual patch number will never reach 500, which has never even come close in 70the past. 71""" 72_NEXT_BUILD_VERSION_CODE_DIFF = 50000 73"""List of version numbers to be created for each build configuration. 74Tuple format: 75 76 (version code name), (package name), (supported ABIs) 77 78Here, (supported ABIs) is referring to the combination of browser ABI and 79webview library ABI present in a particular APK. For example, 64_32 implies a 8064-bit browser with an extra 32-bit Webview library. See also 81_ABIS_TO_DIGIT_MASK. 82""" 83_APKS = { 84 '32': [ 85 ('CHROME', 'CHROME', '32'), 86 ('CHROME_MODERN', 'CHROME_MODERN', '32'), 87 ('MONOCHROME', 'MONOCHROME', '32'), 88 ('TRICHROME', 'TRICHROME', '32'), 89 ('TRICHROME_AUTO', 'TRICHROME_AUTO', '32'), 90 ('TRICHROME_BETA', 'TRICHROME_BETA', '32'), 91 ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32'), 92 ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32'), 93 ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32'), 94 ], 95 '64': [ 96 ('CHROME', 'CHROME', '64'), 97 ('CHROME_MODERN', 'CHROME_MODERN', '64'), 98 ('MONOCHROME', 'MONOCHROME', '64'), 99 ('TRICHROME', 'TRICHROME', '64'), 100 ('TRICHROME_AUTO', 'TRICHROME_AUTO', '64'), 101 ('TRICHROME_BETA', 'TRICHROME_BETA', '64'), 102 ('TRICHROME_DESKTOP', 'TRICHROME_DESKTOP', '64'), 103 ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '64'), 104 ('WEBVIEW_BETA', 'WEBVIEW_BETA', '64'), 105 ('WEBVIEW_DEV', 'WEBVIEW_DEV', '64'), 106 ], 107 'hybrid': [ 108 ('CHROME', 'CHROME', '64'), 109 ('CHROME_32', 'CHROME', '32'), 110 ('CHROME_MODERN', 'CHROME_MODERN', '64'), 111 ('MONOCHROME', 'MONOCHROME', '32_64'), 112 ('MONOCHROME_32', 'MONOCHROME', '32'), 113 ('MONOCHROME_32_64', 'MONOCHROME', '32_64'), 114 ('MONOCHROME_64_32', 'MONOCHROME', '64_32'), 115 ('MONOCHROME_64', 'MONOCHROME', '64'), 116 ('TRICHROME', 'TRICHROME', '32_64'), 117 ('TRICHROME_32', 'TRICHROME', '32'), 118 ('TRICHROME_32_64', 'TRICHROME', '32_64'), 119 ('TRICHROME_64_32', 'TRICHROME', '64_32'), 120 ('TRICHROME_64_32_HIGH', 'TRICHROME', '64_32_high'), 121 ('TRICHROME_64', 'TRICHROME', '64'), 122 ('TRICHROME_AUTO', 'TRICHROME_AUTO', '32_64'), 123 ('TRICHROME_AUTO_32', 'TRICHROME_AUTO', '32'), 124 ('TRICHROME_AUTO_32_64', 'TRICHROME_AUTO', '32_64'), 125 ('TRICHROME_AUTO_64', 'TRICHROME_AUTO', '64'), 126 ('TRICHROME_AUTO_64_32', 'TRICHROME_AUTO', '64_32'), 127 ('TRICHROME_AUTO_64_32_HIGH', 'TRICHROME_AUTO', '64_32_high'), 128 ('TRICHROME_BETA', 'TRICHROME_BETA', '32_64'), 129 ('TRICHROME_32_BETA', 'TRICHROME_BETA', '32'), 130 ('TRICHROME_32_64_BETA', 'TRICHROME_BETA', '32_64'), 131 ('TRICHROME_64_32_BETA', 'TRICHROME_BETA', '64_32'), 132 ('TRICHROME_64_32_HIGH_BETA', 'TRICHROME_BETA', '64_32_high'), 133 ('TRICHROME_DESKTOP_64', 'TRICHROME_DESKTOP', '64'), 134 ('TRICHROME_64_BETA', 'TRICHROME_BETA', '64'), 135 ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32_64'), 136 ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32_64'), 137 ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32_64'), 138 ('WEBVIEW_32_STABLE', 'WEBVIEW_STABLE', '32'), 139 ('WEBVIEW_32_BETA', 'WEBVIEW_BETA', '32'), 140 ('WEBVIEW_32_DEV', 'WEBVIEW_DEV', '32'), 141 ('WEBVIEW_64_STABLE', 'WEBVIEW_STABLE', '64'), 142 ('WEBVIEW_64_BETA', 'WEBVIEW_BETA', '64'), 143 ('WEBVIEW_64_DEV', 'WEBVIEW_DEV', '64'), 144 ] 145} 146 147# Splits input build config architecture to manufacturer and bitness. 148_ARCH_TO_MFG_AND_BITNESS = { 149 'arm': ('arm', '32'), 150 'arm64': ('arm', 'hybrid'), 151 # Until riscv64 needs a unique version code to ship APKs to the store, 152 # point to the 'arm' bitmask. 153 'riscv64': ('arm', '64'), 154 'x86': ('intel', '32'), 155 'x64': ('intel', 'hybrid'), 156} 157 158# Expose the available choices to other scripts. 159ARCH_CHOICES = _ARCH_TO_MFG_AND_BITNESS.keys() 160""" 161The architecture preference is encoded into the version_code for devices 162that support multiple architectures. (exploiting play store logic that pushes 163apk with highest version code) 164 165Detail: 166Many Android devices support multiple architectures, and can run applications 167built for any of them; the Play Store considers all of the supported 168architectures compatible and does not, itself, have any preference for which 169is "better". The common cases here: 170 171- All production arm64 devices can also run arm 172- All production x64 devices can also run x86 173- Pretty much all production x86/x64 devices can also run arm (via a binary 174 translator) 175 176Since the Play Store has no particular preferences, you have to encode your own 177preferences into the ordering of the version codes. There's a few relevant 178things here: 179 180- For any android app, it's theoretically preferable to ship a 64-bit version to 181 64-bit devices if it exists, because the 64-bit architectures are supposed to 182 be "better" than their 32-bit predecessors (unfortunately this is not always 183 true due to the effect on memory usage, but we currently deal with this by 184 simply not shipping a 64-bit version *at all* on the configurations where we 185 want the 32-bit version to be used). 186- For any android app, it's definitely preferable to ship an x86 version to x86 187 devices if it exists instead of an arm version, because running things through 188 the binary translator is a performance hit. 189- For WebView, Monochrome, and Trichrome specifically, they are a special class 190 of APK called "multiarch" which means that they actually need to *use* more 191 than one architecture at runtime (rather than simply being compatible with 192 more than one). The 64-bit builds of these multiarch APKs contain both 32-bit 193 and 64-bit code, so that Webview is available for both ABIs. If you're 194 multiarch you *must* have a version that supports both 32-bit and 64-bit 195 version on a 64-bit device, otherwise it won't work properly. So, the 64-bit 196 version needs to be a higher versionCode, as otherwise a 64-bit device would 197 prefer the 32-bit version that does not include any 64-bit code, and fail. 198""" 199 200 201def _GetAbisToDigitMask(build_number, patch_number): 202 """Return the correct digit mask based on build number. 203 204 Updated from build 5750: Some intel devices advertise support for arm, 205 so arm codes must be lower than x86 codes to prevent providing an 206 arm-optimized build to intel devices. 207 208 Returns: 209 A dictionary of architecture mapped to bitness 210 mapped to version code suffix. 211 """ 212 # Scheme change was made directly to M113 and M114 branches. 213 use_new_scheme = (build_number >= 5750 214 or (build_number == 5672 and patch_number >= 176) 215 or (build_number == 5735 and patch_number >= 53)) 216 if use_new_scheme: 217 return { 218 'arm': { 219 '32': 0, 220 '32_64': 1, 221 '64_32': 2, 222 '64_32_high': 3, 223 '64': 4, 224 }, 225 'intel': { 226 '32': 6, 227 '32_64': 7, 228 '64_32': 8, 229 '64': 9, 230 }, 231 } 232 return { 233 'arm': { 234 '32': 0, 235 '32_64': 3, 236 '64_32': 4, 237 '64': 5, 238 '64_32_high': 9, 239 }, 240 'intel': { 241 '32': 1, 242 '32_64': 6, 243 '64_32': 7, 244 '64': 8, 245 }, 246 } 247 248 249VersionCodeComponents = namedtuple('VersionCodeComponents', [ 250 'build_number', 251 'patch_number', 252 'package_name', 253 'abi', 254 'is_next_build', 255]) 256 257 258def TranslateVersionCode(version_code, is_webview=False): 259 """Translates a version code to its component parts. 260 261 Returns: 262 A 5-tuple (VersionCodeComponents) with the form: 263 - Build number - integer 264 - Patch number - integer 265 - Package name - string 266 - ABI - string : if the build is 32_64 or 64_32 or 64, that is just 267 appended to 'arm' or 'x86' with an underscore 268 - Whether the build is a "next" build - boolean 269 270 So, for build 100.0.5678.99, built for Monochrome on arm 64_32, not a next 271 build, you should get: 272 5678, 99, 'MONOCHROME', 'arm_64_32', False 273 """ 274 if len(version_code) == 9: 275 build_number = int(version_code[:4]) 276 else: 277 # At one branch per day, we'll hit 5 digits in the year 2035. 278 build_number = int(version_code[:5]) 279 280 is_next_build = False 281 patch_number_plus_extra = int(version_code[-5:]) 282 if patch_number_plus_extra >= _NEXT_BUILD_VERSION_CODE_DIFF: 283 is_next_build = True 284 patch_number_plus_extra -= _NEXT_BUILD_VERSION_CODE_DIFF 285 patch_number = patch_number_plus_extra // 100 286 287 # From branch 3992 the name and abi bits in the version code are swapped. 288 if build_number >= 3992: 289 abi_digit = int(version_code[-1]) 290 package_digit = int(version_code[-2]) 291 else: 292 abi_digit = int(version_code[-2]) 293 package_digit = int(version_code[-1]) 294 295 # Before branch 4844 we added 5 to the package digit to indicate a 'next' 296 # build. 297 if build_number < 4844 and package_digit >= 5: 298 is_next_build = True 299 package_digit -= 5 300 301 for package, number in _PACKAGE_NAMES.items(): 302 if number == package_digit * 10: 303 if is_webview == ('WEBVIEW' in package): 304 package_name = package 305 break 306 307 for arch, bitness_to_number in (_GetAbisToDigitMask(build_number, 308 patch_number).items()): 309 for bitness, number in bitness_to_number.items(): 310 if abi_digit == number: 311 abi = arch if arch != 'intel' else 'x86' 312 if bitness != '32': 313 abi += '_' + bitness 314 break 315 316 return VersionCodeComponents(build_number, patch_number, package_name, abi, 317 is_next_build) 318 319 320def GenerateVersionCodes(build_number, patch_number, arch, is_next_build): 321 """Build dict of version codes for the specified build architecture. Eg: 322 323 { 324 'CHROME_VERSION_CODE': '378100010', 325 'MONOCHROME_VERSION_CODE': '378100013', 326 ... 327 } 328 329 versionCode values are built like this: 330 {full BUILD int}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}. 331 332 MAJOR and MINOR values are not used for generating versionCode. 333 - MINOR is always 0. It was used for something long ago in Chrome's history 334 but has not been used since, and has never been nonzero on Android. 335 - MAJOR is cosmetic and controlled by the release managers. MAJOR and BUILD 336 always have reasonable sort ordering: for two version codes A and B, it's 337 always the case that (A.MAJOR < B.MAJOR) implies (A.BUILD < B.BUILD), and 338 that (A.MAJOR > B.MAJOR) implies (A.BUILD > B.BUILD). This property is just 339 maintained by the humans who set MAJOR. 340 341 Thus, this method is responsible for the final two digits of versionCode. 342 """ 343 base_version_code = (build_number * 1000 + patch_number) * 100 344 345 if is_next_build: 346 base_version_code += _NEXT_BUILD_VERSION_CODE_DIFF 347 348 mfg, bitness = _ARCH_TO_MFG_AND_BITNESS[arch] 349 350 version_codes = {} 351 352 abi_to_digit_mask = _GetAbisToDigitMask(build_number, patch_number) 353 for apk, package, abis in _APKS[bitness]: 354 if abis == '64_32_high' and arch != 'arm64': 355 continue 356 abi_part = abi_to_digit_mask[mfg][abis] 357 package_part = _PACKAGE_NAMES[package] 358 359 version_code_name = apk + '_VERSION_CODE' 360 version_code_val = base_version_code + package_part + abi_part 361 version_codes[version_code_name] = str(version_code_val) 362 363 return version_codes 364 365 366def main(): 367 parser = argparse.ArgumentParser(description='Parses version codes.') 368 g1 = parser.add_argument_group('To Generate Version Name') 369 g1.add_argument('--version-code', help='Version code (e.g. 529700010).') 370 g1.add_argument('--webview', 371 action='store_true', 372 help='Whether this is a webview version code.') 373 g2 = parser.add_argument_group('To Generate Version Code') 374 g2.add_argument('--version-name', help='Version name (e.g. 124.0.6355.0).') 375 g2.add_argument('--arch', 376 choices=ARCH_CHOICES, 377 help='Set which cpu architecture the build is for.') 378 g2.add_argument('--next', 379 action='store_true', 380 help='Whether the current build should be a "next" ' 381 'build, which targets pre-release versions of Android.') 382 args = parser.parse_args() 383 if args.version_code: 384 print(TranslateVersionCode(args.version_code, is_webview=args.webview)) 385 elif args.version_name: 386 if not args.arch: 387 parser.error('Required --arch') 388 _, _, build, patch = args.version_name.split('.') 389 values = GenerateVersionCodes(int(build), int(patch), args.arch, args.next) 390 for k, v in values.items(): 391 print(f'{k}={v}') 392 else: 393 parser.print_help() 394 395 396 397if __name__ == '__main__': 398 main() 399