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