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()