-
Notifications
You must be signed in to change notification settings - Fork 814
Expand file tree
/
Copy pathbuild.gradle.kts
More file actions
398 lines (355 loc) · 18.7 KB
/
Copy pathbuild.gradle.kts
File metadata and controls
398 lines (355 loc) · 18.7 KB
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
392
393
394
395
396
397
398
import com.android.build.api.dsl.ApplicationDefaultConfig
import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.api.AndroidBasePlugin
import com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
plugins {
alias(libs.plugins.agp.lib) apply false
alias(libs.plugins.agp.app) apply false
// Declaring the Kotlin plugin here pins the version on the buildscript classpath for
// every module. AGP 9 otherwise supplies its own, older Kotlin, and a module that
// asks for a specific version fails with "already on the classpath with an unknown
// version". The Compose stack in :manager needs the newer compiler.
alias(libs.plugins.kotlin) apply false
alias(libs.plugins.ktfmt)
}
/** A ValueSource that executes 'git rev-list --count' to get the total commit count. */
abstract class GitCommitCountValueSource : ValueSource<String, ValueSourceParameters.None> {
@get:Inject abstract val execOperations: ExecOperations
override fun obtain(): String {
val output = ByteArrayOutputStream()
val result = execOperations.exec {
commandLine("git", "rev-list", "--count", "refs/remotes/origin/master")
standardOutput = output
isIgnoreExitValue = true
}
// Return the count if successful, otherwise a default of "1".
return if (result.exitValue == 0 && output.toString().isNotBlank()) {
output.toString().trim()
} else {
"1"
}
}
}
/** A ValueSource that executes 'git tag' to get the latest version tag. */
abstract class GitLatestTagValueSource : ValueSource<String, ValueSourceParameters.None> {
@get:Inject abstract val execOperations: ExecOperations
override fun obtain(): String {
val output = ByteArrayOutputStream()
val result = execOperations.exec {
commandLine("git", "tag", "--list", "--sort=-v:refname")
standardOutput = output
isIgnoreExitValue = true
}
// If successful, parse the first line. Provide a default if no tags are found.
return if (result.exitValue == 0 && output.toString().isNotBlank()) {
output.toString().lineSequence().first().removePrefix("v")
} else {
"1.0"
}
}
}
/**
* Which build this is, in one string: what commit it came from and where it was built.
*
* The version code is the commit count on origin/master, so every branch build carries master's
* number: a build flashed from a feature branch and one flashed from master both report "v2.0
* (3052)" and cannot be told apart on the device. That is not hypothetical — it cost a real
* investigation to establish which of the two was installed.
*
* **The commit always comes first, and what follows always says where.** Both readers of this
* string want the commit and nothing else — the manager compares it against the SHA a release was
* cut from to answer "am I running this build", and the status page sets it in a type the rest is
* not given — and for both it is now simply the head of the string. No rule has to work out which
* part is which, so none can get it wrong when a repository or a machine turns out to have a hyphen
* in its name.
*
* The separator says which kind of "where" follows, because they mean opposite things about whether
* the commit describes the binary:
*
* *On GitHub Actions*, `93d66473-JingMatrix-Vector` — the commit, then the repository the code came
* from. Forks build the same code from the same commits, so the hash alone identifies a *revision*
* and not a *build*, and a fork's artifact was indistinguishable from ours.
*
* Both halves are the *head* ones, and neither is read from the environment GitHub sets by default,
* because on a pull request both defaults name something other than the code that was built:
*
* `GITHUB_REPOSITORY` is the repository that *ran* the workflow. A pull request from a fork is
* built here, so it would stamp `JingMatrix/Vector` onto a branch that has never been near it —
* which is exactly the artifact the name most needs to distinguish.
*
* `HEAD` is worse. For a pull request the runner checks out an ephemeral merge of the branch into
* the base, so `git rev-parse HEAD` names a commit that exists in no repository, cannot be looked
* up by anyone who reads it off a device, and is gone once the run is. The commit that was pushed
* is the one that identifies the build.
*
* So the workflow passes both in: `VECTOR_BUILD_REPOSITORY` and `VECTOR_BUILD_COMMIT`, each taken
* from `github.event.pull_request.head.*` when there is a pull request and from the ordinary
* default when there is not — outside a pull request the two agree anyway. Absent either, this
* falls back to what the environment says and to `HEAD`, which covers a workflow too old to set
* them.
*
* *Locally*, the bare `93d66473`, or `93d66473+thinkpad` when the tree has uncommitted changes.
* That build corresponds to no commit at all, so naming the commit alone would name something the
* binary does not match — and whoever is looking at it is far better served by the machine it was
* built on than by the word "dirty", since a device that has a hand-built framework on it usually
* has exactly one candidate author.
*
* `+` rather than `-` for that one, in semver's sense of build metadata: after a `-` is a
* repository that holds this exact commit, and after a `+` is a change that is in no repository at
* all. That is the one distinction a reader of the stamp cannot afford to lose — a modified build
* matches no release, and read as a CI build it would claim to be the release it was merely started
* from. Neither character can occur inside a host name or an `owner/repo`, so the two shapes stay
* apart.
*
* The dirty check is deliberately not applied on CI. The workflow's own "Write key" step appends
* the signing credentials to the tracked `gradle.properties` before Gradle starts, so every master
* and tag build reports a modified tree — every published canary has said `-dirty` for that reason
* and no other, which sent at least one user looking for a fault that was not there (#815).
*
* The result goes in module.prop's human-readable version and in BuildConfig.VERSION_HASH, which
* the manager shows on the status page. BuildConfig.VERSION_NAME is deliberately left clean.
*/
abstract class GitCommitHashValueSource : ValueSource<String, GitCommitHashValueSource.Parameters> {
interface Parameters : ValueSourceParameters {
/**
* `owner/repo` when GitHub Actions is building this, empty otherwise.
*
* Threaded in as parameters rather than read with `System.getenv` inside [obtain], which
* the configuration cache does not see and therefore does not invalidate on.
*/
val buildRepository: Property<String>
/** The full SHA that was pushed, when CI knows one and it is not what is checked out. */
val buildCommit: Property<String>
}
@get:Inject abstract val execOperations: ExecOperations
/**
* Runs [command] and returns its trimmed output, or null if it failed or said nothing.
*
* `exec` *throws* when the executable cannot be started at all, which `isIgnoreExitValue` does
* not cover — `hostname` is not on every machine — so the whole call is wrapped rather than
* just its exit code inspected.
*/
private fun capture(vararg command: String): String? =
runCatching {
val output = ByteArrayOutputStream()
val result = execOperations.exec {
commandLine(command.toList())
standardOutput = output
errorOutput = ByteArrayOutputStream()
isIgnoreExitValue = true
}
if (result.exitValue != 0) null else output.toString().trim().ifBlank { null }
}
.getOrNull()
/**
* The machine's name, reduced to what is safe in a version string.
*
* A host name can carry anything the owner typed into it, and this value is interpolated into
* module.prop and a generated Kotlin string literal. Unknown hosts fall back to "local", which
* still reads correctly: not a CI build, and not a clean tree.
*/
private fun hostname(): String {
val raw = capture("hostname") ?: System.getenv("HOSTNAME") ?: return "local"
// The short name only. A fully qualified host name is mostly domain, and the domain is the
// part that is identical across every machine that would ever build this.
val short = raw.substringBefore('.')
val safe = short.filter { it.isLetterOrDigit() || it == '-' || it == '_' }
return safe.ifBlank { "local" }
}
override fun obtain(): String {
// Abbreviated by git rather than by hand, so a CI build and a local build of the same
// commit
// read identically however long this repository's abbreviation happens to be.
val head = capture("git", "rev-parse", "--short", "HEAD") ?: return "unknown"
val repository = parameters.buildRepository.getOrElse("")
if (repository.isBlank()) {
val dirty = capture("git", "status", "--porcelain", "--untracked-files=no") != null
return if (dirty) "$head+${hostname()}" else head
}
// The pushed commit is an ancestor of the merge that was checked out, so it is in the
// repository and git will abbreviate it. The truncation is only reached if it somehow is
// not, and a slightly odd length beats reporting the merge commit or nothing at all.
val pushed = parameters.buildCommit.getOrElse("").takeIf { it.isNotBlank() }
val short =
pushed?.let { capture("git", "rev-parse", "--short", it) ?: it.take(head.length) }
?: head
return short + "-" + repository.replace('/', '-')
}
}
// Plain vals plus an explicit `extra.set`, rather than the `by extra(...)` delegate: Gradle 9.6
// deprecated that syntax and drops it in Gradle 10, and every one of these declarations printed a
// deprecation warning of its own. The subprojects read them out of `rootProject.extra` by name, so
// the string here and the property name have to stay in step by hand now.
//
// This defers the execution of the git commands and allows Gradle to cache the results.
val versionCodeProvider = providers.of(GitCommitCountValueSource::class.java) {}
val versionHashProvider =
providers.of(GitCommitHashValueSource::class.java) {
// Set on every GitHub Actions runner and on nothing else, so the presence of either is
// the test for "this is a CI build". The workflow's own variables win because they name
// the branch that was pushed; GitHub's defaults name the run that built it.
parameters.buildRepository.set(
providers
.environmentVariable("VECTOR_BUILD_REPOSITORY")
.orElse(providers.environmentVariable("GITHUB_REPOSITORY"))
.orElse("")
)
parameters.buildCommit.set(providers.environmentVariable("VECTOR_BUILD_COMMIT").orElse(""))
}
val versionNameProvider = providers.of(GitLatestTagValueSource::class.java) {}
val injectedPackageName = "com.android.shell"
val injectedPackageUid = 2000
val defaultManagerPackageName = "org.matrix.vector.manager"
val androidTargetSdkVersion = 37
val androidMinSdkVersion = 27
val androidBuildToolsVersion = "37.0.0"
val androidCompileSdkVersion = 37
val androidCompileNdkVersion = "29.0.14206865"
val androidSourceCompatibility = JavaVersion.VERSION_21
val androidTargetCompatibility = JavaVersion.VERSION_21
extra.set("versionCodeProvider", versionCodeProvider)
extra.set("versionHashProvider", versionHashProvider)
extra.set("versionNameProvider", versionNameProvider)
extra.set("injectedPackageName", injectedPackageName)
extra.set("injectedPackageUid", injectedPackageUid)
extra.set("defaultManagerPackageName", defaultManagerPackageName)
extra.set("androidTargetSdkVersion", androidTargetSdkVersion)
extra.set("androidMinSdkVersion", androidMinSdkVersion)
extra.set("androidBuildToolsVersion", androidBuildToolsVersion)
extra.set("androidCompileSdkVersion", androidCompileSdkVersion)
extra.set("androidCompileNdkVersion", androidCompileNdkVersion)
extra.set("androidSourceCompatibility", androidSourceCompatibility)
extra.set("androidTargetCompatibility", androidTargetCompatibility)
subprojects {
plugins.withType(AndroidBasePlugin::class.java) {
extensions.configure(CommonExtension::class.java) {
compileSdk = androidCompileSdkVersion
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion
buildFeatures.buildConfig = true
externalNativeBuild.cmake {
version = "3.29.8+"
buildStagingDirectory = layout.buildDirectory.get().asFile
}
defaultConfig.apply {
minSdk = androidMinSdkVersion
ndk { abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")) }
if (this is ApplicationDefaultConfig) {
targetSdk = androidTargetSdkVersion
versionCode = versionCodeProvider.get().toInt()
versionName = versionNameProvider.get()
}
val flags =
listOf(
"-DVERSION_CODE=${versionCodeProvider.get()}",
"-DVERSION_NAME='\"${versionNameProvider.get()}\"'",
// parallel_hashmap reaches for <emmintrin.h> whenever __SSE2__ is defined,
// and that header's static inline intrinsics arrive twice on the x86 ABIs:
// dex_builder.ixx and dex_helper.ixx each include phmap in their global
// module fragment, so importing dex_builder gives clang two definitions
// with the same mangled name. clang 21 (NDK r29) rejects that outright,
// where clang 20 merged them. Turning phmap's SSE2 group scan off costs
// nothing on arm, which never had it, and is set for every native module
// rather than for dex_builder alone because phmap's layout depends on the
// flag -- our own hook_bridge.cpp instantiates the same templates, and two
// group sizes in one .so would be an ODR violation the linker cannot see.
"-DPHMAP_HAVE_SSE2=0",
// phmap refuses to configure with SSSE3 but no SSE2, so both go together.
"-DPHMAP_HAVE_SSSE3=0",
)
val args =
listOf(
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
"-DVECTOR_ROOT=${rootDir.absolutePath}",
// Enforce 16 KB page size alignment for Android 15+ compatibility
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384",
"-DCMAKE_EXE_LINKER_FLAGS=-Wl,-z,max-page-size=16384",
)
externalNativeBuild {
cmake {
cFlags.addAll(flags)
cppFlags.addAll(flags)
arguments.addAll(args)
}
}
}
buildTypes.getByName("release").apply {
externalNativeBuild {
cmake {
arguments.add(
"-DDEBUG_SYMBOLS_PATH=${
layout.buildDirectory.dir("symbols").get().asFile.absolutePath
}"
)
}
}
}
lint.apply {
abortOnError = true
checkReleaseBuilds = false
}
compileOptions.apply {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}
}
}
plugins.withType(JavaPlugin::class.java) {
extensions.configure(JavaPluginExtension::class.java) {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}
}
// Java that is not ours to modernise, and that javac otherwise comments on twice per build --
// once per build type, in both the debug and the release halves of `zipAll`.
//
// `:external:apache` and `:external:axml` compile vendored upstream sources (commons-lang and
// ManifestEditor); `:services:*` compile the libxposed submodule and the AIDL stubs AGP
// generates, neither of which we write. `:legacy` is the de.robv API surface itself: XResources
// exists precisely to override `getColor`, `getDrawable` and the rest of the deprecated
// Resources methods, so every one of those overrides is deliberate and none can be dropped
// while the legacy API is supported.
//
// `-nowarn` alone is not enough. The "Note: Some input files use or override a deprecated API"
// line is a *mandatory* warning summary, emitted precisely because the lint is off, and only
// `-XDsuppressNotes` silences it.
val vendoredJava =
setOf(
":external:apache",
":external:axml",
":legacy",
":services:daemon-service",
":services:manager-service",
)
if (path in vendoredJava) {
tasks.withType(JavaCompile::class.java).configureEach {
options.compilerArgs.addAll(listOf("-nowarn", "-XDsuppressNotes"))
}
}
}
tasks.register<KtfmtFormatTask>("format") {
source = project.fileTree(rootDir)
include(
"*.gradle.kts",
"*/build.gradle.kts",
"hiddenapi/*/build.gradle.kts",
"services/*-service/build.gradle.kts",
)
// The daemon subproject is stuck on ktfmt's default (Meta) style instead of the
// kotlinLangStyle() applied everywhere else — the wrong style was set for it, but a
// bulk reformat would wreck git blame across the module, so it is kept as-is. Exclude
// its build script here so this task's kotlinLangStyle sweep does not fight
// :daemon:ktfmtFormat, which formats the daemon (scripts included) in Meta style.
exclude("daemon/**")
dependsOn(":daemon:ktfmtFormat")
dependsOn(":manager:ktfmtFormat")
dependsOn(":xposed:ktfmtFormat")
dependsOn(":zygisk:ktfmtFormat")
}
ktfmt { kotlinLangStyle() }