Build script adjustments

This commit is contained in:
topjohnwu 2023-03-02 20:32:13 -08:00 committed by John Wu
parent 307cf87215
commit 8adf27859d
3 changed files with 138 additions and 124 deletions

View File

@ -1,10 +1,11 @@
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.InputStream
import java.io.PrintStream import java.io.PrintStream
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@ -78,6 +79,9 @@ fun genKeyData(keysDir: File, outSrc: File) {
@CacheableTask @CacheableTask
abstract class ManifestUpdater: DefaultTask() { abstract class ManifestUpdater: DefaultTask() {
@get:Input
abstract val applicationId: Property<String>
@get:InputFile @get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE) @get:PathSensitive(PathSensitivity.RELATIVE)
abstract val mergedManifest: RegularFileProperty abstract val mergedManifest: RegularFileProperty
@ -99,74 +103,68 @@ abstract class ManifestUpdater: DefaultTask() {
val cmpList = mutableListOf<String>() val cmpList = mutableListOf<String>()
cmpList.add( cmpList.add("""
""" |<provider
|<provider | android:name="x.COMPONENT_PLACEHOLDER_0"
| android:name="x.COMPONENT_PLACEHOLDER_0" | android:authorities="${'$'}{applicationId}.provider"
| android:authorities="${'$'}{applicationId}.provider" | android:directBootAware="true"
| android:directBootAware="true" | android:exported="false"
| android:exported="false" | android:grantUriPermissions="true" />""".ind(2)
| android:grantUriPermissions="true" />""".ind(2)
) )
cmpList.add( cmpList.add("""
""" |<receiver
|<receiver | android:name="x.COMPONENT_PLACEHOLDER_1"
| android:name="x.COMPONENT_PLACEHOLDER_1" | android:exported="false">
| android:exported="false"> | <intent-filter>
| <intent-filter> | <action android:name="android.intent.action.LOCALE_CHANGED" />
| <action android:name="android.intent.action.LOCALE_CHANGED" /> | <action android:name="android.intent.action.UID_REMOVED" />
| <action android:name="android.intent.action.UID_REMOVED" /> | <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
| <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> | </intent-filter>
| </intent-filter> | <intent-filter>
| <intent-filter> | <action android:name="android.intent.action.PACKAGE_REPLACED" />
| <action android:name="android.intent.action.PACKAGE_REPLACED" /> | <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
| <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> |
| | <data android:scheme="package" />
| <data android:scheme="package" /> | </intent-filter>
| </intent-filter> |</receiver>""".ind(2)
|</receiver>""".ind(2)
) )
cmpList.add( cmpList.add("""
""" |<activity
|<activity | android:name="x.COMPONENT_PLACEHOLDER_2"
| android:name="x.COMPONENT_PLACEHOLDER_2" | android:exported="true">
| android:exported="true"> | <intent-filter>
| <intent-filter> | <action android:name="android.intent.action.MAIN" />
| <action android:name="android.intent.action.MAIN" /> | <category android:name="android.intent.category.LAUNCHER" />
| <category android:name="android.intent.category.LAUNCHER" /> | </intent-filter>
| </intent-filter> |</activity>""".ind(2)
|</activity>""".ind(2)
) )
cmpList.add( cmpList.add("""
""" |<activity
|<activity | android:name="x.COMPONENT_PLACEHOLDER_3"
| android:name="x.COMPONENT_PLACEHOLDER_3" | android:directBootAware="true"
| android:directBootAware="true" | android:exported="false"
| android:exported="false" | android:taskAffinity="">
| android:taskAffinity=""> | <intent-filter>
| <intent-filter> | <action android:name="android.intent.action.VIEW"/>
| <action android:name="android.intent.action.VIEW"/> | <category android:name="android.intent.category.DEFAULT"/>
| <category android:name="android.intent.category.DEFAULT"/> | </intent-filter>
| </intent-filter> |</activity>""".ind(2)
|</activity>""".ind(2)
) )
cmpList.add( cmpList.add("""
""" |<service
|<service | android:name="x.COMPONENT_PLACEHOLDER_4"
| android:name="x.COMPONENT_PLACEHOLDER_4" | android:exported="false" />""".ind(2)
| android:exported="false" />""".ind(2)
) )
cmpList.add( cmpList.add("""
""" |<service
|<service | android:name="x.COMPONENT_PLACEHOLDER_5"
| android:name="x.COMPONENT_PLACEHOLDER_5" | android:exported="false"
| android:exported="false" | android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
) )
// Shuffle the order of the components // Shuffle the order of the components
@ -177,15 +175,19 @@ abstract class ManifestUpdater: DefaultTask() {
val (appPkg, appClass) = appClassDir.asFileTree.first().let { val (appPkg, appClass) = appClassDir.asFileTree.first().let {
it.parentFile.name to it.name.removeSuffix(".java") it.parentFile.name to it.name.removeSuffix(".java")
} }
var manifest = mergedManifest.asFile.get().readText() val components = cmpList.joinToString("\n\n")
manifest = manifest.replace("<application", """<application android:appComponentFactory="$factoryPkg.$factoryClass" android:name="$appPkg.$appClass"""") .replace("\${applicationId}", applicationId.get())
manifest = manifest.replace("</application", "${cmpList.joinToString("\n\n")}\n</application") val manifest = mergedManifest.asFile.get().readText().replace(Regex(".*\\<application"), """
|<application
| android:appComponentFactory="$factoryPkg.$factoryClass"
| android:name="$appPkg.$appClass"""".ind(1)
).replace(Regex(".*\\<\\/application"), components + "\n </application")
outputManifest.get().asFile.writeText(manifest) outputManifest.get().asFile.writeText(manifest)
} }
} }
fun genStubClass(factoryOutDir: File, appOutDir: File) { fun genStubClasses(factoryOutDir: File, appOutDir: File) {
fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level)) fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level))
val classNameGenerator = sequence { val classNameGenerator = sequence {
@ -231,7 +233,7 @@ fun genStubClass(factoryOutDir: File, appOutDir: File) {
genClass("DelegateApplication", appOutDir) genClass("DelegateApplication", appOutDir)
} }
fun genEncryptedResources(res: InputStream, outDir: File) { fun genEncryptedResources(res: ByteArray, outDir: File) {
val mainPkgDir = File(outDir, "com/topjohnwu/magisk") val mainPkgDir = File(outDir, "com/topjohnwu/magisk")
mainPkgDir.mkdirs() mainPkgDir.mkdirs()
@ -245,7 +247,7 @@ fun genEncryptedResources(res: InputStream, outDir: File) {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
res.use { ByteArrayInputStream(res).use {
CipherOutputStream(bos, cipher).use { os -> CipherOutputStream(bos, cipher).use { os ->
it.transferTo(os) it.transferTo(os)
} }

View File

@ -17,18 +17,33 @@ import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.plugins.ExtensionAware import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.tasks.* import org.gradle.api.tasks.Delete
import org.gradle.kotlin.dsl.* import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.filter
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.registering
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.security.KeyStore import java.security.KeyStore
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.*
import java.util.jar.JarFile import java.util.jar.JarFile
import java.util.zip.* import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
private fun Project.androidBase(configure: Action<BaseExtension>) = private fun Project.androidBase(configure: Action<BaseExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
@ -88,30 +103,6 @@ private fun ApkSigningConfig.getPrivateKey(): KeyStore.PrivateKeyEntry {
return entry as KeyStore.PrivateKeyEntry return entry as KeyStore.PrivateKeyEntry
} }
private fun addComment(inFile: File, outFile: File, signConfig: ApkSigningConfig, minSdk: Int, eocdComment: String) {
val privateKey = signConfig.getPrivateKey()
val signingOptions = SigningOptions.builder()
.setMinSdkVersion(minSdk)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setKey(privateKey.privateKey)
.setCertificates(privateKey.certificate as X509Certificate)
.setValidation(SigningOptions.Validation.ASSUME_INVALID)
.build()
val options = ZFileOptions().apply {
noTimestamps = true
autoSortFiles = true
}
outFile.parentFile.mkdirs()
inFile.copyTo(outFile, overwrite = true)
ZFiles.apk(outFile, options).use {
SigningExtension(signingOptions).register(it)
it.eocdComment = eocdComment.toByteArray()
it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete()
it.get(JarFile.MANIFEST_NAME)?.delete()
}
}
abstract class AddCommentTask: DefaultTask() { abstract class AddCommentTask: DefaultTask() {
@get:Input @get:Input
abstract val comment: Property<String> abstract val comment: Property<String>
@ -132,7 +123,29 @@ abstract class AddCommentTask: DefaultTask() {
fun taskAction() = transformationRequest.get().submit(this) { artifact -> fun taskAction() = transformationRequest.get().submit(this) { artifact ->
val inFile = File(artifact.outputFile) val inFile = File(artifact.outputFile)
val outFile = outFolder.file(inFile.name).get().asFile val outFile = outFolder.file(inFile.name).get().asFile
addComment(inFile, outFile, signingConfig.get(), 0, comment.get())
val privateKey = signingConfig.get().getPrivateKey()
val signingOptions = SigningOptions.builder()
.setMinSdkVersion(0)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setKey(privateKey.privateKey)
.setCertificates(privateKey.certificate as X509Certificate)
.setValidation(SigningOptions.Validation.ASSUME_INVALID)
.build()
val options = ZFileOptions().apply {
noTimestamps = true
autoSortFiles = true
}
outFile.parentFile.mkdirs()
inFile.copyTo(outFile, overwrite = true)
ZFiles.apk(outFile, options).use {
SigningExtension(signingOptions).register(it)
it.eocdComment = comment.get().toByteArray()
it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete()
it.get(JarFile.MANIFEST_NAME)?.delete()
}
outFile outFile
} }
} }
@ -305,16 +318,15 @@ fun Project.setupApp() {
fun Project.setupStub() { fun Project.setupStub() {
setupAppCommon() setupAppCommon()
androidComponents.onVariants { variant -> androidComponents.onVariants { variant ->
val variantName = variant.name
val variantCapped = variantName.replaceFirstChar { it.uppercase() }
val manifestUpdater = val manifestUpdater =
project.tasks.register( project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) {
"${variant.name}ManifestProducer", dependsOn("generate${variantCapped}ObfuscatedClass")
ManifestUpdater::class.java applicationId.set(variant.applicationId)
) { appClassDir.set(File(buildDir, "generated/source/app/$variantName"))
dependsOn("generate${variant.name.replaceFirstChar { it.uppercase() }}ObfuscatedClass") factoryClassDir.set(File(buildDir, "generated/source/factory/$variantName"))
appClassDir.set(File(buildDir, "generated/source/app/${variant.name}"))
factoryClassDir.set(File(buildDir, "generated/source/factory/${variant.name}"))
} }
variant.artifacts.use(manifestUpdater) variant.artifacts.use(manifestUpdater)
.wiredWithFiles( .wiredWithFiles(
@ -340,33 +352,34 @@ fun Project.setupStub() {
doLast { doLast {
outFactoryClassDir.mkdirs() outFactoryClassDir.mkdirs()
outAppClassDir.mkdirs() outAppClassDir.mkdirs()
genStubClass(outFactoryClassDir, outAppClassDir) genStubClasses(outFactoryClassDir, outAppClassDir)
} }
} }
registerJavaGeneratingTask(genManifestTask, outFactoryClassDir, outAppClassDir) registerJavaGeneratingTask(genManifestTask, outFactoryClassDir, outAppClassDir)
val processResourcesTask = tasks.getByPath(":stub:process${variantCapped}Resources") val processResourcesTask = tasks.named("process${variantCapped}Resources") {
processResourcesTask.doLast { doLast {
exec { exec {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk) commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
} }
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
ZipFile(apkTmp).use { src -> ZipFile(apkTmp).use { src ->
ZipOutputStream(apk.outputStream()).use { ZipOutputStream(apk.outputStream()).use {
it.setLevel(Deflater.BEST_COMPRESSION) it.setLevel(Deflater.BEST_COMPRESSION)
it.putNextEntry(ZipEntry("AndroidManifest.xml")) it.putNextEntry(ZipEntry("AndroidManifest.xml"))
src.getInputStream(src.getEntry("AndroidManifest.xml")).transferTo(it) src.getInputStream(src.getEntry("AndroidManifest.xml")).transferTo(it)
it.closeEntry() it.closeEntry()
} }
DeflaterOutputStream(bos, Deflater(Deflater.BEST_COMPRESSION)).use { DeflaterOutputStream(bos, Deflater(Deflater.BEST_COMPRESSION)).use {
src.getInputStream(src.getEntry("resources.arsc")).transferTo(it) src.getInputStream(src.getEntry("resources.arsc")).transferTo(it)
}
} }
apkTmp.delete()
genEncryptedResources(bos.toByteArray(), outResDir)
} }
apkTmp.delete()
genEncryptedResources(ByteArrayInputStream(bos.toByteArray()), outResDir)
} }
@Suppress("DEPRECATION")
registerJavaGeneratingTask(processResourcesTask, outResDir) registerJavaGeneratingTask(processResourcesTask, outResDir)
} }
// Override optimizeReleaseResources task // Override optimizeReleaseResources task

View File

@ -7,8 +7,7 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<application <application tools:ignore="MissingApplicationIcon">
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon,UnusedAttribute">
</application> </application>
</manifest> </manifest>