versionCode.gradle (3893B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import java.text.SimpleDateFormat 6 7 /** 8 * Generates a "unique" versionCode for release builds. 9 * 10 * The resulting versionCode depends on the local timezone of the machine running this script. 11 * This is OK because we only use this for release builds on CI, where the timezone is fixed. 12 * 13 * Format: byDDDHHmm 14 * - b = base / epoch digit 15 * Historically hardcoded to "3". This digit is incremented when the year-derived 16 * component overflows its single digit (e.g., in 2026). 17 * - y = 1 digit derived from (two-digit year - 16), modulo 10 18 * - DDD = day of year (001–366), zero-padded to 3 digits 19 * - HHmm = 24h time (00–23)(00–59) 20 * 21 * Example: 22 * Sept 6, 2017 @ 09:41 23 * year = 17 - 16 = 1 24 * base = 3 25 * -> 3-1-249-09-41 -> 312490941 26 * 27 * Historical note: 28 * Focus first shipped in 2017. The original scheme unconditionally used (yy - 16) which 29 * only fit in a single digit from 2017–2025. 30 * 31 * 2026 rollover: 32 * In 2026, (yy - 16) became 10. Allowing this to grow to two digits breaks the intended 33 * byDDDHHmm layout and can exceed Play / int limits. 34 * 35 * To preserve: 36 * - a single-digit `y` 37 * - monotonic versionCodes across year boundaries 38 * 39 * we keep `y` as (yearOffset % 10) and carry overflow into the base digit: 40 * 2025 -> base=3, y=9 -> 39DDDHHmm 41 * 2026 -> base=4, y=0 -> 40DDDHHmm 42 */ 43 ext { 44 // "Epoch" digit(s). Historically this was "3". 45 // We bump it by +1 each time (yy - 16) crosses another multiple of 10 (i.e., 2026, 2036, ...). 46 def epochDigit = 3 47 48 def today = new Date() 49 50 def yy = (new SimpleDateFormat("yy").format(today) as int) 51 def yearOffset = yy - 16 // 2017 -> 1, 2025 -> 9, 2026 -> 10, etc. 52 if (yearOffset < 0) { 53 throw new GradleException( 54 "versionCode yearOffset underflow: yearOffset=$yearOffset (yy=$yy)." 55 ) 56 } 57 58 // Keep the "y" component as one digit, and carry overflow into the epoch digit. 59 def carry = (int) (yearOffset / 10) 60 def yearDigit = (int) (yearOffset % 10) 61 62 def epoch = epochDigit + carry 63 if (epoch >= 10) { 64 throw new GradleException( 65 "versionCode epoch overflow: epoch=$epoch (yy=$yy). Update versionCode scheme." 66 ) 67 } 68 69 // We use the day in the Year (e.g. 248) as opposed to month + day (0510) because it's one digit shorter. 70 // If needed we pad with zeros (e.g. 25 -> 025) 71 def day = String.format("%03d", (new SimpleDateFormat("D").format(today) as int)) 72 73 // We append the hour in day (24h) and minute in hour (7:26 pm -> 1926). We do not append 74 // seconds. This assumes that we do not need to build multiple release(!) builds the same 75 // minute. 76 def time = new SimpleDateFormat("HHmm").format(today) 77 78 // Build the final versionCode using the previously-calculated inputs. 79 def versionCode = ("${epoch}${yearDigit}${day}${time}" as long) 80 81 // The Play Console has historically enforced a 2,100,000,000 cap. Keep a defensive ceiling here. 82 // Even without this, Android requires versionCode to fit in a signed 32-bit int. 83 def MAX_VERSION_CODE = 2_100_000_000 84 if (versionCode > MAX_VERSION_CODE) { 85 throw new GradleException( 86 "Generated versionCode exceeds MAX_VERSION_CODE ($MAX_VERSION_CODE): $versionCode (from $versionCodeStr)" 87 ) 88 } 89 if (versionCode > Integer.MAX_VALUE) { 90 throw new GradleException( 91 "Generated versionCode exceeds Integer.MAX_VALUE: $versionCode (from $versionCodeStr)" 92 ) 93 } 94 95 generatedVersionCode = (versionCode as int) 96 println("Generated versionCode: $generatedVersionCode") 97 println() 98 }