[fenix] For https://github.com/mozilla-mobile/fenix/issues/21084: add Config.getGitHash.
[gecko.git] / mobile / android / fenix / buildSrc / src / main / java / Config.kt
blobef27174f3099338bb9f2579288321b2959b78435
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/. */
5 import org.gradle.api.Project
6 import org.mozilla.fenix.gradle.ext.execReadStandardOutOrThrow
7 import java.text.SimpleDateFormat
8 import java.time.LocalDateTime
9 import java.util.Date
10 import java.util.Locale
12 object Config {
13     // Synchronized build configuration for all modules
14     const val compileSdkVersion = 31
15     const val minSdkVersion = 21
16     const val targetSdkVersion = 30
18     @JvmStatic
19     private fun generateDebugVersionName(): String {
20         val today = Date()
21         // Append the year (2 digits) and week in year (2 digits). This will make it easier to distinguish versions and
22         // identify ancient versions when debugging issues. However this will still keep the same version number during
23         // the week so that we do not end up with a lot of versions in tools like Sentry. As an extra this matches the
24         // sections we use in the changelog (weeks).
25         return SimpleDateFormat("1.0.yyww", Locale.US).format(today)
26     }
28     @JvmStatic
29     fun releaseVersionName(project: Project): String {
30         // Note: release builds must have the `versionName` set. However, the gradle ecosystem makes this hard to
31         // ergonomically validate (sometimes IDEs default to a release variant and mysteriously fail due to the
32         // validation, sometimes devs just need a release build and specifying project properties is annoying in IDEs),
33         // so instead we'll allow the `versionName` to silently default to an empty string.
34         return if (project.hasProperty("versionName")) project.property("versionName") as String else ""
35     }
37     @JvmStatic
38     fun nightlyVersionName(): String {
39         // Nightly versions use the Gecko/A-C major version and append "0.a1", e.g. with A-C 90.0.20210426143115
40         // the Nightly version will be 90.0a1
41         val majorVersion = AndroidComponents.VERSION.split(".")[0]
42         return "$majorVersion.0a1"
43     }
45     /**
46      * Generate a build date that follows the ISO-8601 format
47      */
48     @JvmStatic
49     fun generateBuildDate(): String {
50         return LocalDateTime.now().toString()
51     }
53     private val fennecBaseVersionCode by lazy {
54         val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.US)
55         val cutoff = format.parse("20141228000000")
56         val build = Date()
58         Math.floor((build.time - cutoff.time) / (1000.0 * 60.0 * 60.0)).toInt()
59     }
61     /**
62      * Generates a versionCode that follows the same rules like legacy Fennec builds.
63      * Adapted from:
64      * https://searchfox.org/mozilla-central/rev/34cb8d0a2a324043bcfc2c56f37b31abe7fb23a8/python/mozbuild/mozbuild/android_version_code.py
65      *
66      * There is a discrepancy between the epoch date used here (20141228)
67      * and the epoch used in Fennec (20150801) for historical reasons. We keep
68      * this discrepancy to avoid having Fenix version codes decrease.
69      * Note that the original Fennec implementation also had an inconsistency in
70      * the documented epoch date (20150901) and the effective epoch date (20150801).
71      */
72     @JvmStatic
73     fun generateFennecVersionCode(abi: String): Int {
74         // The important consideration is that version codes be monotonically
75         // increasing (per Android package name) for all published builds.  The input
76         // build IDs are based on timestamps and hence are always monotonically
77         // increasing.
78         //
79         //         The generated v1 version codes look like (in binary):
80         //
81         // 0111 1000 0010 tttt tttt tttt tttt txpg
82         //
83         // The 17 bits labelled 't' represent the number of hours since midnight on
84         // December 28, 2014.  (2014122800 in yyyyMMddHH format.)  This yields a
85         // little under 15 years worth of hourly build identifiers, since 2**17 / (366
86         //         * 24) =~ 14.92.
87         //
88         //         The bits labelled 'x', 'p', and 'g' are feature flags.
89         //
90         // The bit labelled 'x' is 1 if the build is for an x86 or x86-64 architecture,
91         // and 0 otherwise, which means the build is for an ARM or ARM64 architecture.
92         // (Fennec no longer supports ARMv6, so ARM is equivalent to ARMv7.
93         //
94         //         ARM64 is also known as AArch64; it is logically ARMv8.)
95         //
96         // For the same release, x86 and x86_64 builds have higher version codes and
97         // take precedence over ARM builds, so that they are preferred over ARM on
98         // devices that have ARM emulation.
99         //
100         // The bit labelled 'p' is 1 if the build is for a 64-bit architecture (x86-64
101         //         or ARM64), and 0 otherwise, which means the build is for a 32-bit
102         // architecture (x86 or ARM). 64-bit builds have higher version codes so
103         // they take precedence over 32-bit builds on devices that support 64-bit.
104         //
105         //         The bit labelled 'g' is 1 was used for APK splits and is
106         //         nowadays always set to 1 until it serves a new purpose.
107         //
108         // We throw an explanatory exception when we are within one calendar year of
109         // running out of build events.  This gives lots of time to update the version
110         // scheme.  The responsible individual should then bump the range (to allow
111         //         builds to continue) and use the time remaining to update the version scheme
112         // via the reserved high order bits.
113         //
114         //         N.B.: the reserved 0 bit to the left of the highest order 't' bit can,
115         //         sometimes, be used to bump the version scheme.  In addition, by reducing the
116         // granularity of the build identifiers (for example, moving to identifying
117         // builds every 2 or 4 hours), the version scheme may be adjusted further still
118         // without losing a (valuable) high order bit.
120         val base = fennecBaseVersionCode
122         when {
123             base < 0 -> throw RuntimeException("Cannot calculate versionCode. Hours underflow.")
124             base > 0x20000 /* 2^17 */ -> throw RuntimeException("Cannot calculate versionCode. Hours overflow.")
125             base > 0x20000 - (366 * 24) ->
126                 // You have one year to update the version scheme...
127                 throw RuntimeException("Running out of low order bits calculating versionCode.")
128         }
130         var version = 0x78200000 // 1111000001000000000000000000000
131         // We reserve 1 "middle" high order bit for the future, and 3 low order bits
132         // for architecture and APK splits.
133         version = version or (base shl 3)
135         // 'x' bit is 1 for x86/x86-64 architectures
136         if (abi == "x86_64" || abi == "x86") {
137             version = version or (1 shl 2)
138         }
140         // 'p' bit is 1 for 64-bit architectures.
141         if (abi == "arm64-v8a" || abi == "x86_64") {
142             version = version or (1 shl 1)
143         }
145         // 'g' bit is currently always 1 (see comment above)
146         version = version or (1 shl 0)
148         return version
149     }
151     /**
152      * Returns the git hash of the currently checked out revision. If there are uncommitted changes,
153      * a "+" will be appended to the hash, e.g. "c8ba05ad0+".
154      */
155     @JvmStatic
156     fun getGitHash(): String {
157         val revisionCmd = arrayOf("git", "rev-parse", "--short", "HEAD")
158         val revision = Runtime.getRuntime().execReadStandardOutOrThrow(revisionCmd)
160         // Append "+" if there are uncommitted changes in the working directory.
161         val statusCmd = arrayOf("git", "status", "--porcelain=v2")
162         val status = Runtime.getRuntime().execReadStandardOutOrThrow(statusCmd)
163         val hasUnstagedChanges = status.isNotBlank()
164         val statusSuffix = if (hasUnstagedChanges) "+" else ""
166         return "${revision}${statusSuffix}"
167     }