blob: d1489f5f36bc6198d591b2300b2772f747a8668b [file] [log] [blame]
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function
import argparse
import math
import sys
import time
# Builds before this build ID use the v0 version scheme. Builds after this
# build ID use the v1 version scheme.
V1_CUTOFF = 20150801000000 # YYYYmmddHHMMSS
def android_version_code_v0(buildid, cpu_arch=None, min_sdk=0, max_sdk=0):
base = int(str(buildid)[:10])
# None is interpreted as arm.
if not cpu_arch or cpu_arch in ['armeabi', 'armeabi-v7a']:
# Increment by MIN_SDK_VERSION -- this adds 9 to every build ID as a
# minimum. Our split APK starts at 11.
return base + min_sdk + 0
elif cpu_arch in ['x86']:
# Increment the version code by 3 for x86 builds so they are offered to
# x86 phones that have ARM emulators, beating the 2-point advantage that
# the v11+ ARMv7 APK has. If we change our splits in the future, we'll
# need to do this further still.
return base + min_sdk + 3
else:
raise ValueError("Don't know how to compute android:versionCode "
"for CPU arch %s" % cpu_arch)
def android_version_code_v1(buildid, cpu_arch=None, min_sdk=0, max_sdk=0):
'''Generate a v1 android:versionCode.
The important consideration is that version codes be monotonically
increasing (per Android package name) for all published builds. The input
build IDs are based on timestamps and hence are always monotonically
increasing.
The generated v1 version codes look like (in binary):
0111 1000 0010 tttt tttt tttt tttt txpg
The 17 bits labelled 't' represent the number of hours since midnight on
September 1, 2015. (2015090100 in YYYYMMMDDHH format.) This yields a
little under 15 years worth of hourly build identifiers, since 2**17 / (366
* 24) =~ 14.92.
The bits labelled 'x', 'p', and 'g' are feature flags.
The bit labelled 'x' is 1 if the build is for an x86 architecture and 0
otherwise, which means the build is for an ARM architecture. (Fennec no
longer supports ARMv6, so ARM is equivalent to ARMv7 and above.)
The bit labelled 'p' is a placeholder that is always 0 (for now).
The bit labelled 'g' is 1 if the build is targeting Android API 11/14+ and 0
otherwise, which means the build targets Android API 9-10 (Gingerbread).
Fennec no longer supports Android API 8 or earlier. After Bug 1155801 it
no longer supports API 11-13. API 9 is still supported due to significant usage.
We temporarily treat both 11 and 14 the same: Bug 1219512.
We throw an explanatory exception when we are within one calendar year of
running out of build events. This gives lots of time to update the version
scheme. The responsible individual should then bump the range (to allow
builds to continue) and use the time remaining to update the version scheme
via the reserved high order bits.
N.B.: the reserved 0 bit to the left of the highest order 't' bit can,
sometimes, be used to bump the version scheme. In addition, by reducing the
granularity of the build identifiers (for example, moving to identifying
builds every 2 or 4 hours), the version scheme may be adjusted further still
without losing a (valuable) high order bit.
'''
def hours_since_cutoff(buildid):
# The ID is formatted like YYYYMMDDHHMMSS (using
# datetime.now().strftime('%Y%m%d%H%M%S'); see
# toolkit/xre/make-platformini.py). The inverse function is
# time.strptime. N.B.: the time module expresses time as decimal
# seconds since the epoch.
fmt = '%Y%m%d%H%M%S'
build = time.strptime(str(buildid), fmt)
cutoff = time.strptime(str(V1_CUTOFF), fmt)
return int(math.floor((time.mktime(build) - time.mktime(cutoff)) / (60.0 * 60.0)))
# Of the 21 low order bits, we take 17 bits for builds.
base = hours_since_cutoff(buildid)
if base < 0:
raise ValueError("Something has gone horribly wrong: cannot calculate "
"android:versionCode from build ID %s: hours underflow "
"bits allotted!" % buildid)
if base > 2**17:
raise ValueError("Something has gone horribly wrong: cannot calculate "
"android:versionCode from build ID %s: hours overflow "
"bits allotted!" % buildid)
if base > 2**17 - 366 * 24:
raise ValueError("Running out of low order bits calculating "
"android:versionCode from build ID %s: "
"; YOU HAVE ONE YEAR TO UPDATE THE VERSION SCHEME." % buildid)
version = 0b1111000001000000000000000000000
# We reserve 1 "middle" high order bit for the future, and 3 low order bits
# for architecture and APK splits.
version |= base << 3
# None is interpreted as arm.
if not cpu_arch or cpu_arch in ['armeabi', 'armeabi-v7a']:
# 0 is interpreted as SDK 9.
if not min_sdk or min_sdk == 9:
pass
# This used to compare to 11. The 14+ APK directly supersedes 11+, so
# we reuse this check.
elif min_sdk == 14 or min_sdk == 11:
version |= 1 << 0
else:
raise ValueError("Don't know how to compute android:versionCode "
"for CPU arch %s and min SDK %s" % (cpu_arch, min_sdk))
elif cpu_arch in ['x86']:
version |= 1 << 2
else:
raise ValueError("Don't know how to compute android:versionCode "
"for CPU arch %s" % cpu_arch)
return version
def android_version_code(buildid, *args, **kwargs):
base = int(str(buildid))
if base < V1_CUTOFF:
return android_version_code_v0(buildid, *args, **kwargs)
else:
return android_version_code_v1(buildid, *args, **kwargs)
def main(argv):
parser = argparse.ArgumentParser('Generate an android:versionCode',
add_help=False)
parser.add_argument('--verbose', action='store_true',
default=False,
help='Be verbose')
parser.add_argument('--with-android-cpu-arch', dest='cpu_arch',
choices=['armeabi', 'armeabi-v7a', 'mips', 'x86'],
help='The target CPU architecture')
parser.add_argument('--with-android-min-sdk-version', dest='min_sdk',
type=int, default=0,
help='The minimum target SDK')
parser.add_argument('--with-android-max-sdk-version', dest='max_sdk',
type=int, default=0,
help='The maximum target SDK')
parser.add_argument('buildid', type=int,
help='The input build ID')
args = parser.parse_args(argv)
code = android_version_code(args.buildid,
cpu_arch=args.cpu_arch,
min_sdk=args.min_sdk,
max_sdk=args.max_sdk)
print(code)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))