/* 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/. */ import java.text.SimpleDateFormat /** * Generates a "unique" versionCode for release builds. * * The resulting versionCode depends on the local timezone of the machine running this script. * This is OK because we only use this for release builds on CI, where the timezone is fixed. * * Format: byDDDHHmm * - b = base / epoch digit * Historically hardcoded to "3". This digit is incremented when the year-derived * component overflows its single digit (e.g., in 2026). * - y = 1 digit derived from (two-digit year - 16), modulo 10 * - DDD = day of year (001–366), zero-padded to 3 digits * - HHmm = 24h time (00–23)(00–59) * * Example: * Sept 6, 2017 @ 09:41 * year = 17 - 16 = 1 * base = 3 * -> 3-1-249-09-41 -> 312490941 * * Historical note: * Focus first shipped in 2017. The original scheme unconditionally used (yy - 16) which * only fit in a single digit from 2017–2025. * * 2026 rollover: * In 2026, (yy - 16) became 10. Allowing this to grow to two digits breaks the intended * byDDDHHmm layout and can exceed Play / int limits. * * To preserve: * - a single-digit `y` * - monotonic versionCodes across year boundaries * * we keep `y` as (yearOffset % 10) and carry overflow into the base digit: * 2025 -> base=3, y=9 -> 39DDDHHmm * 2026 -> base=4, y=0 -> 40DDDHHmm */ ext { // "Epoch" digit(s). Historically this was "3". // We bump it by +1 each time (yy - 16) crosses another multiple of 10 (i.e., 2026, 2036, ...). def epochDigit = 3 def today = new Date() def yy = (new SimpleDateFormat("yy").format(today) as int) def yearOffset = yy - 16 // 2017 -> 1, 2025 -> 9, 2026 -> 10, etc. if (yearOffset < 0) { throw new GradleException( "versionCode yearOffset underflow: yearOffset=$yearOffset (yy=$yy)." ) } // Keep the "y" component as one digit, and carry overflow into the epoch digit. def carry = (int) (yearOffset / 10) def yearDigit = (int) (yearOffset % 10) def epoch = epochDigit + carry if (epoch >= 10) { throw new GradleException( "versionCode epoch overflow: epoch=$epoch (yy=$yy). Update versionCode scheme." ) } // We use the day in the Year (e.g. 248) as opposed to month + day (0510) because it's one digit shorter. // If needed we pad with zeros (e.g. 25 -> 025) def day = String.format("%03d", (new SimpleDateFormat("D").format(today) as int)) // We append the hour in day (24h) and minute in hour (7:26 pm -> 1926). We do not append // seconds. This assumes that we do not need to build multiple release(!) builds the same // minute. def time = new SimpleDateFormat("HHmm").format(today) // Build the final versionCode using the previously-calculated inputs. def versionCode = ("${epoch}${yearDigit}${day}${time}" as long) // The Play Console has historically enforced a 2,100,000,000 cap. Keep a defensive ceiling here. // Even without this, Android requires versionCode to fit in a signed 32-bit int. def MAX_VERSION_CODE = 2_100_000_000 if (versionCode > MAX_VERSION_CODE) { throw new GradleException( "Generated versionCode exceeds MAX_VERSION_CODE ($MAX_VERSION_CODE): $versionCode (from $versionCodeStr)" ) } if (versionCode > Integer.MAX_VALUE) { throw new GradleException( "Generated versionCode exceeds Integer.MAX_VALUE: $versionCode (from $versionCodeStr)" ) } generatedVersionCode = (versionCode as int) println("Generated versionCode: $generatedVersionCode") println() }