1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344
  345
  346
  347
  348
  349
  350
  351
  352
  353
  354
  355
  356
  357
  358
  359
  360
  361
  362
  363
  364
  365
  366
  367
  368
  369
  370
  371
  372
  373
  374
  375
  376
  377
  378
  379
  380
  381
  382
  383
  384
  385
  386
  387
  388
  389
  390
  391

build / util / android_chrome_version.py [blame]

#!/usr/bin/env python3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Different build variants of Chrome for Android have different version codes.

For targets that have the same package name (e.g. Chrome, Chrome Modern,
Monochrome, Trichrome), Play Store considers them the same app and will push the
supported app with the highest version code to devices. Note that Play Store
does not support hosting two different apps with same version code and package
name.

Each version code generated by this script will be used by one or more APKs.

Webview channels must have unique version codes for a couple reasons:
a) Play Store does not support having the same version code for different
   versions of a package. Without unique codes, promoting a beta apk to stable
   would require first removing the beta version.
b) Firebase project support (used by official builders) requires unique
   [version code + package name].
   We cannot add new webview package names for new channels because webview
   packages are allowlisted by Android as webview providers.

WEBVIEW_STABLE, WEBVIEW_BETA, WEBVIEW_DEV are all used for standalone webview,
whereas the others are used for various chrome APKs.

TRICHROME_BETA is used for TrichromeChrome, TrichromeWebView, and
TrichromeLibrary when these are compiled to use the stable package name. Similar
to how WEBVIEW_STABLE/WEBVIEW_BETA work, this allows users to opt into the open
Beta Track for the stable package. When Trichrome is configured to use a
distinct package name for the Beta package, the version code will use TRICHROME
instead of TRICHROME_BETA.

Note that a package digit of '3' for Webview is reserved for Trichrome Webview.
The same versionCode is used for both Trichrome Chrome and Trichrome Webview.

Version code values are constructed like this:

  {full BUILD number}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}.

For example:

  Build 3721, patch 0, ChromeModern (1), on ARM64 (5): 372100015
  Build 3721, patch 9, Monochrome (2), on ARM (0): 372100920

"""

import argparse
from collections import namedtuple

# Package name version bits.
_PACKAGE_NAMES = {
    'CHROME': 0,
    'CHROME_MODERN': 10,
    'MONOCHROME': 20,
    'TRICHROME': 30,
    'TRICHROME_BETA': 40,
    'TRICHROME_AUTO': 50,
    'TRICHROME_DESKTOP': 60,
    'WEBVIEW_STABLE': 0,
    'WEBVIEW_BETA': 10,
    'WEBVIEW_DEV': 20,
}
""" "Next" builds get +500 on their patch number.

This ensures that they are considered "newer" than any non-next build of the
same branch number; this is a workaround for Android requiring a total ordering
of versions when we only really have a partial ordering. This assumes that the
actual patch number will never reach 500, which has never even come close in
the past.
"""
_NEXT_BUILD_VERSION_CODE_DIFF = 50000
"""List of version numbers to be created for each build configuration.
Tuple format:

  (version code name), (package name), (supported ABIs)

Here, (supported ABIs) is referring to the combination of browser ABI and
webview library ABI present in a particular APK. For example, 64_32 implies a
64-bit browser with an extra 32-bit Webview library. See also
_ABIS_TO_DIGIT_MASK.
"""
_APKS = {
    '32': [
        ('CHROME', 'CHROME', '32'),
        ('CHROME_MODERN', 'CHROME_MODERN', '32'),
        ('MONOCHROME', 'MONOCHROME', '32'),
        ('TRICHROME', 'TRICHROME', '32'),
        ('TRICHROME_AUTO', 'TRICHROME_AUTO', '32'),
        ('TRICHROME_BETA', 'TRICHROME_BETA', '32'),
        ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32'),
        ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32'),
        ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32'),
    ],
    '64': [
        ('CHROME', 'CHROME', '64'),
        ('CHROME_MODERN', 'CHROME_MODERN', '64'),
        ('MONOCHROME', 'MONOCHROME', '64'),
        ('TRICHROME', 'TRICHROME', '64'),
        ('TRICHROME_AUTO', 'TRICHROME_AUTO', '64'),
        ('TRICHROME_BETA', 'TRICHROME_BETA', '64'),
        ('TRICHROME_DESKTOP', 'TRICHROME_DESKTOP', '64'),
        ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '64'),
        ('WEBVIEW_BETA', 'WEBVIEW_BETA', '64'),
        ('WEBVIEW_DEV', 'WEBVIEW_DEV', '64'),
    ],
    'hybrid': [
        ('CHROME', 'CHROME', '64'),
        ('CHROME_32', 'CHROME', '32'),
        ('CHROME_MODERN', 'CHROME_MODERN', '64'),
        ('MONOCHROME', 'MONOCHROME', '32_64'),
        ('MONOCHROME_32', 'MONOCHROME', '32'),
        ('MONOCHROME_32_64', 'MONOCHROME', '32_64'),
        ('MONOCHROME_64_32', 'MONOCHROME', '64_32'),
        ('MONOCHROME_64', 'MONOCHROME', '64'),
        ('TRICHROME', 'TRICHROME', '32_64'),
        ('TRICHROME_32', 'TRICHROME', '32'),
        ('TRICHROME_32_64', 'TRICHROME', '32_64'),
        ('TRICHROME_64_32', 'TRICHROME', '64_32'),
        ('TRICHROME_64_32_HIGH', 'TRICHROME', '64_32_high'),
        ('TRICHROME_64', 'TRICHROME', '64'),
        ('TRICHROME_AUTO', 'TRICHROME_AUTO', '32_64'),
        ('TRICHROME_AUTO_32', 'TRICHROME_AUTO', '32'),
        ('TRICHROME_AUTO_32_64', 'TRICHROME_AUTO', '32_64'),
        ('TRICHROME_AUTO_64', 'TRICHROME_AUTO', '64'),
        ('TRICHROME_AUTO_64_32', 'TRICHROME_AUTO', '64_32'),
        ('TRICHROME_AUTO_64_32_HIGH', 'TRICHROME_AUTO', '64_32_high'),
        ('TRICHROME_BETA', 'TRICHROME_BETA', '32_64'),
        ('TRICHROME_32_BETA', 'TRICHROME_BETA', '32'),
        ('TRICHROME_32_64_BETA', 'TRICHROME_BETA', '32_64'),
        ('TRICHROME_64_32_BETA', 'TRICHROME_BETA', '64_32'),
        ('TRICHROME_64_32_HIGH_BETA', 'TRICHROME_BETA', '64_32_high'),
        ('TRICHROME_DESKTOP_64', 'TRICHROME_DESKTOP', '64'),
        ('TRICHROME_64_BETA', 'TRICHROME_BETA', '64'),
        ('WEBVIEW_STABLE', 'WEBVIEW_STABLE', '32_64'),
        ('WEBVIEW_BETA', 'WEBVIEW_BETA', '32_64'),
        ('WEBVIEW_DEV', 'WEBVIEW_DEV', '32_64'),
        ('WEBVIEW_32_STABLE', 'WEBVIEW_STABLE', '32'),
        ('WEBVIEW_32_BETA', 'WEBVIEW_BETA', '32'),
        ('WEBVIEW_32_DEV', 'WEBVIEW_DEV', '32'),
        ('WEBVIEW_64_STABLE', 'WEBVIEW_STABLE', '64'),
        ('WEBVIEW_64_BETA', 'WEBVIEW_BETA', '64'),
        ('WEBVIEW_64_DEV', 'WEBVIEW_DEV', '64'),
    ]
}

# Splits input build config architecture to manufacturer and bitness.
_ARCH_TO_MFG_AND_BITNESS = {
    'arm': ('arm', '32'),
    'arm64': ('arm', 'hybrid'),
    # Until riscv64 needs a unique version code to ship APKs to the store,
    # point to the 'arm' bitmask.
    'riscv64': ('arm', '64'),
    'x86': ('intel', '32'),
    'x64': ('intel', 'hybrid'),
}

# Expose the available choices to other scripts.
ARCH_CHOICES = _ARCH_TO_MFG_AND_BITNESS.keys()
"""
The architecture preference is encoded into the version_code for devices
that support multiple architectures. (exploiting play store logic that pushes
apk with highest version code)

Detail:
Many Android devices support multiple architectures, and can run applications
built for any of them; the Play Store considers all of the supported
architectures compatible and does not, itself, have any preference for which
is "better". The common cases here:

- All production arm64 devices can also run arm
- All production x64 devices can also run x86
- Pretty much all production x86/x64 devices can also run arm (via a binary
  translator)

Since the Play Store has no particular preferences, you have to encode your own
preferences into the ordering of the version codes. There's a few relevant
things here:

- For any android app, it's theoretically preferable to ship a 64-bit version to
  64-bit devices if it exists, because the 64-bit architectures are supposed to
  be "better" than their 32-bit predecessors (unfortunately this is not always
  true due to the effect on memory usage, but we currently deal with this by
  simply not shipping a 64-bit version *at all* on the configurations where we
  want the 32-bit version to be used).
- For any android app, it's definitely preferable to ship an x86 version to x86
  devices if it exists instead of an arm version, because running things through
  the binary translator is a performance hit.
- For WebView, Monochrome, and Trichrome specifically, they are a special class
  of APK called "multiarch" which means that they actually need to *use* more
  than one architecture at runtime (rather than simply being compatible with
  more than one). The 64-bit builds of these multiarch APKs contain both 32-bit
  and 64-bit code, so that Webview is available for both ABIs. If you're
  multiarch you *must* have a version that supports both 32-bit and 64-bit
  version on a 64-bit device, otherwise it won't work properly. So, the 64-bit
  version needs to be a higher versionCode, as otherwise a 64-bit device would
  prefer the 32-bit version that does not include any 64-bit code, and fail.
"""


def _GetAbisToDigitMask(build_number, patch_number):
  """Return the correct digit mask based on build number.

  Updated from build 5750: Some intel devices advertise support for arm,
  so arm codes must be lower than x86 codes to prevent providing an
  arm-optimized build to intel devices.

  Returns:
    A dictionary of architecture mapped to bitness
    mapped to version code suffix.
  """
  # Scheme change was made directly to M113 and M114 branches.
  use_new_scheme = (build_number >= 5750
                    or (build_number == 5672 and patch_number >= 176)
                    or (build_number == 5735 and patch_number >= 53))
  if use_new_scheme:
    return {
        'arm': {
            '32': 0,
            '32_64': 1,
            '64_32': 2,
            '64_32_high': 3,
            '64': 4,
        },
        'intel': {
            '32': 6,
            '32_64': 7,
            '64_32': 8,
            '64': 9,
        },
    }
  return {
      'arm': {
          '32': 0,
          '32_64': 3,
          '64_32': 4,
          '64': 5,
          '64_32_high': 9,
      },
      'intel': {
          '32': 1,
          '32_64': 6,
          '64_32': 7,
          '64': 8,
      },
  }


VersionCodeComponents = namedtuple('VersionCodeComponents', [
    'build_number',
    'patch_number',
    'package_name',
    'abi',
    'is_next_build',
])


def TranslateVersionCode(version_code, is_webview=False):
  """Translates a version code to its component parts.

  Returns:
    A 5-tuple (VersionCodeComponents) with the form:
      - Build number - integer
      - Patch number - integer
      - Package name - string
      - ABI - string : if the build is 32_64 or 64_32 or 64, that is just
                       appended to 'arm' or 'x86' with an underscore
      - Whether the build is a "next" build - boolean

    So, for build 100.0.5678.99, built for Monochrome on arm 64_32, not a next
    build, you should get:
      5678, 99, 'MONOCHROME', 'arm_64_32', False
  """
  if len(version_code) == 9:
    build_number = int(version_code[:4])
  else:
    # At one branch per day, we'll hit 5 digits in the year 2035.
    build_number = int(version_code[:5])

  is_next_build = False
  patch_number_plus_extra = int(version_code[-5:])
  if patch_number_plus_extra >= _NEXT_BUILD_VERSION_CODE_DIFF:
    is_next_build = True
    patch_number_plus_extra -= _NEXT_BUILD_VERSION_CODE_DIFF
  patch_number = patch_number_plus_extra // 100

  # From branch 3992 the name and abi bits in the version code are swapped.
  if build_number >= 3992:
    abi_digit = int(version_code[-1])
    package_digit = int(version_code[-2])
  else:
    abi_digit = int(version_code[-2])
    package_digit = int(version_code[-1])

  # Before branch 4844 we added 5 to the package digit to indicate a 'next'
  # build.
  if build_number < 4844 and package_digit >= 5:
    is_next_build = True
    package_digit -= 5

  for package, number in _PACKAGE_NAMES.items():
    if number == package_digit * 10:
      if is_webview == ('WEBVIEW' in package):
        package_name = package
        break

  for arch, bitness_to_number in (_GetAbisToDigitMask(build_number,
                                                      patch_number).items()):
    for bitness, number in bitness_to_number.items():
      if abi_digit == number:
        abi = arch if arch != 'intel' else 'x86'
        if bitness != '32':
          abi += '_' + bitness
        break

  return VersionCodeComponents(build_number, patch_number, package_name, abi,
                               is_next_build)


def GenerateVersionCodes(build_number, patch_number, arch):
  """Build dict of version codes for the specified build architecture. Eg:

  {
    'CHROME_VERSION_CODE': '378100010',
    'MONOCHROME_VERSION_CODE': '378100013',
    ...
  }

  versionCode values are built like this:
  {full BUILD int}{3 digits: PATCH}{1 digit: package}{1 digit: ABIs}.

  MAJOR and MINOR values are not used for generating versionCode.
  - MINOR is always 0. It was used for something long ago in Chrome's history
    but has not been used since, and has never been nonzero on Android.
  - MAJOR is cosmetic and controlled by the release managers. MAJOR and BUILD
    always have reasonable sort ordering: for two version codes A and B, it's
    always the case that (A.MAJOR < B.MAJOR) implies (A.BUILD < B.BUILD), and
    that (A.MAJOR > B.MAJOR) implies (A.BUILD > B.BUILD). This property is just
    maintained by the humans who set MAJOR.

  Thus, this method is responsible for the final two digits of versionCode.
  """
  base_version_code = (build_number * 1000 + patch_number) * 100

  mfg, bitness = _ARCH_TO_MFG_AND_BITNESS[arch]

  version_codes = {}

  abi_to_digit_mask = _GetAbisToDigitMask(build_number, patch_number)
  for apk, package, abis in _APKS[bitness]:
    if abis == '64_32_high' and arch != 'arm64':
      continue
    abi_part = abi_to_digit_mask[mfg][abis]
    package_part = _PACKAGE_NAMES[package]

    version_code_name = apk + '_VERSION_CODE'
    version_code_val = base_version_code + package_part + abi_part
    version_codes[version_code_name] = str(version_code_val)

  return version_codes


def main():
  parser = argparse.ArgumentParser(description='Parses version codes.')
  g1 = parser.add_argument_group('To Generate Version Name')
  g1.add_argument('--version-code', help='Version code (e.g. 529700010).')
  g1.add_argument('--webview',
                  action='store_true',
                  help='Whether this is a webview version code.')
  g2 = parser.add_argument_group('To Generate Version Code')
  g2.add_argument('--version-name', help='Version name (e.g. 124.0.6355.0).')
  g2.add_argument('--arch',
                  choices=ARCH_CHOICES,
                  help='Set which cpu architecture the build is for.')
  args = parser.parse_args()
  if args.version_code:
    print(TranslateVersionCode(args.version_code, is_webview=args.webview))
  elif args.version_name:
    if not args.arch:
      parser.error('Required --arch')
    _, _, build, patch = args.version_name.split('.')
    values = GenerateVersionCodes(int(build), int(patch), args.arch)
    for k, v in values.items():
      print(f'{k}={v}')
  else:
    parser.print_help()



if __name__ == '__main__':
  main()