|
@@ -0,0 +1,230 @@
|
|
|
+package io.nexilis
|
|
|
+
|
|
|
+import org.gradle.api.Plugin
|
|
|
+import org.gradle.api.initialization.Settings
|
|
|
+import org.w3c.dom.Document
|
|
|
+import org.w3c.dom.Element
|
|
|
+import org.xml.sax.SAXException
|
|
|
+import java.io.File
|
|
|
+import java.io.IOException
|
|
|
+import java.io.StringWriter
|
|
|
+import java.nio.file.FileVisitResult
|
|
|
+import java.nio.file.Files
|
|
|
+import java.nio.file.Path
|
|
|
+import java.nio.file.Paths
|
|
|
+import java.nio.file.SimpleFileVisitor
|
|
|
+import java.nio.file.StandardCopyOption
|
|
|
+import java.nio.file.attribute.BasicFileAttributes
|
|
|
+import javax.xml.parsers.DocumentBuilderFactory
|
|
|
+import javax.xml.parsers.ParserConfigurationException
|
|
|
+import javax.xml.transform.TransformerFactory
|
|
|
+import javax.xml.transform.dom.DOMSource
|
|
|
+import javax.xml.transform.stream.StreamResult
|
|
|
+import kotlin.io.path.ExperimentalPathApi
|
|
|
+import kotlin.io.path.extension
|
|
|
+import kotlin.io.path.inputStream
|
|
|
+import kotlin.io.path.name
|
|
|
+import kotlin.io.path.nameWithoutExtension
|
|
|
+
|
|
|
+const val libNameSpace = "com.example.dm"
|
|
|
+
|
|
|
+class SettingsPlugin: Plugin<Settings> {
|
|
|
+ override fun apply(settings: Settings) {
|
|
|
+ println(">>>>>>>>>>> Settings")
|
|
|
+ settings.gradle.settingsEvaluated {
|
|
|
+ println(">>>>>>>>>>> settingsEvaluated")
|
|
|
+ try {
|
|
|
+ val moduleDir = File(settings.rootDir, "dynamic_core")
|
|
|
+ if (moduleDir.exists()) {
|
|
|
+ moduleDir.deleteRecursively()
|
|
|
+ println(">>>>>>>>>>> delete:dir:${moduleDir.toPath()}")
|
|
|
+ }
|
|
|
+ if (!moduleDir.exists()) {
|
|
|
+ println(">>>>>>>>>>> create:dir:${moduleDir.toPath()}")
|
|
|
+ moduleDir.mkdirs()
|
|
|
+ }
|
|
|
+ val projectApp = settings.project(File(settings.rootDir, "app"))
|
|
|
+ println(">>>>>>>>>>> copy:from:${projectApp.projectDir.toPath()}")
|
|
|
+ projectApp.projectDir.toPath().copy(moduleDir.toPath())
|
|
|
+ println(">>>>>>>>>>> copy:done")
|
|
|
+ moduleDir.toPath().refactor()
|
|
|
+ println(">>>>>>>>>>> refactor:done")
|
|
|
+ settings.include(":dynamic_core")
|
|
|
+ println(">>>>>>>>>>> include module:dynamic_core")
|
|
|
+ val tmpBaseDir = File(projectApp.projectDir, "src/custom/java/io/nexilis/app")
|
|
|
+ println(">>>>>>>>>>> create:base:dir:${tmpBaseDir.toPath()}")
|
|
|
+ if (!tmpBaseDir.exists()) {
|
|
|
+ tmpBaseDir.mkdirs()
|
|
|
+ }
|
|
|
+ val activity = Paths.get(tmpBaseDir.toPath().toString(), "NexilisActivity.java")
|
|
|
+ Files.writeString(activity, mainActivity)
|
|
|
+ println(">>>>>>>>>>> create:activity:$activity")
|
|
|
+ } catch (e: Exception) {
|
|
|
+ e.printStackTrace()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const val mainActivity = "package io.nexilis.app;\n" +
|
|
|
+ "\n" +
|
|
|
+ "import android.os.Bundle;\n" +
|
|
|
+ "\n" +
|
|
|
+ "import androidx.appcompat.app.AppCompatActivity;\n" +
|
|
|
+ "\n" +
|
|
|
+ "public class NexilisActivity extends AppCompatActivity {\n" +
|
|
|
+ "\n" +
|
|
|
+ " @Override\n" +
|
|
|
+ " protected void onCreate(Bundle savedInstanceState) {\n" +
|
|
|
+ " super.onCreate(savedInstanceState);\n" +
|
|
|
+ " }\n" +
|
|
|
+ "\n" +
|
|
|
+ "}\n"
|
|
|
+const val androidManifest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
|
|
|
+ "<manifest xmlns:dist=\"http://schemas.android.com/apk/distribution\">\n" +
|
|
|
+ "\n" +
|
|
|
+ " <dist:module\n" +
|
|
|
+ " dist:instant=\"false\"\n" +
|
|
|
+ " dist:title=\"@string/title_dynamic_core\">\n" +
|
|
|
+ " <dist:delivery>\n" +
|
|
|
+ " <dist:on-demand />\n" +
|
|
|
+ " </dist:delivery>\n" +
|
|
|
+ " <dist:fusing dist:include=\"true\" />\n" +
|
|
|
+ " </dist:module>\n" +
|
|
|
+ " \n" +
|
|
|
+ "</manifest>"
|
|
|
+
|
|
|
+@OptIn(ExperimentalPathApi::class)
|
|
|
+fun Path.copy(target: Path) {
|
|
|
+ Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
|
|
|
+ @Throws(IOException::class)
|
|
|
+ override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
|
|
|
+ if (dir.fileName.name == "build") {
|
|
|
+ return FileVisitResult.SKIP_SUBTREE
|
|
|
+ }
|
|
|
+ val targetPath = target.resolve(this@copy.relativize(dir))
|
|
|
+ if (!Files.exists(targetPath)) {
|
|
|
+ Files.createDirectory(targetPath)
|
|
|
+ }
|
|
|
+ return FileVisitResult.CONTINUE
|
|
|
+ }
|
|
|
+
|
|
|
+ @Throws(IOException::class)
|
|
|
+ override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
|
|
|
+ if (file.fileName.name == "strings.xml") return FileVisitResult.CONTINUE
|
|
|
+ val targetPath = target.resolve(this@copy.relativize(file))
|
|
|
+ Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING)
|
|
|
+ if (file.isResource()) {
|
|
|
+ val oldName = targetPath.fileName.nameWithoutExtension
|
|
|
+ val oldExtension = targetPath.extension
|
|
|
+ val newName = "app_$oldName.$oldExtension"
|
|
|
+ val oldFile = targetPath.toFile()
|
|
|
+ val newFile = File(oldFile.parentFile, newName)
|
|
|
+ oldFile.renameTo(newFile)
|
|
|
+ renamedFiles[oldName] = newName
|
|
|
+ }
|
|
|
+ if (file.fileName.name == "build.gradle") {
|
|
|
+ var updatedContent = """plugins\s*\{[^}]*}""".toRegex()
|
|
|
+ .replace(
|
|
|
+ Files.readString(file).trimIndent(),
|
|
|
+ "plugins {\n id 'com.android.dynamic-feature'\n}\n"
|
|
|
+ )
|
|
|
+// updatedContent = """namespace\s+(['"])[^'"]+"\s*""".toRegex()
|
|
|
+// .replace(updatedContent, "'$libNameSpace'")
|
|
|
+// updatedContent = """applicationId\s+(['"])[^'"]+"\s*""".toRegex()
|
|
|
+// .replace(updatedContent, "")
|
|
|
+ updatedContent = """kotlinOptions\s*\{\s*jvmTarget\s*=\s*"11"\s*\}""".toRegex()
|
|
|
+ .replace(updatedContent, "")
|
|
|
+ Files.writeString(targetPath, updatedContent)
|
|
|
+ }
|
|
|
+ if (file.fileName.name == "AndroidManifest.xml") {
|
|
|
+ try {
|
|
|
+ val activity = file.replaceMainActivity("io.nexilis.app.NexilisActivity")
|
|
|
+ Files.writeString(file, activity)
|
|
|
+ } catch (_: Exception) {
|
|
|
+ }
|
|
|
+ Files.writeString(targetPath, androidManifest)
|
|
|
+ }
|
|
|
+ return FileVisitResult.CONTINUE
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+fun Path.refactor() {
|
|
|
+ Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
|
|
|
+ override fun visitFile(p0: Path, p1: BasicFileAttributes?): FileVisitResult {
|
|
|
+ if (p0.isSource()) {
|
|
|
+ var isReplace: Boolean = false
|
|
|
+ var content = Files.readString(p0)
|
|
|
+ renamedFiles.forEach { (oldName, newName) ->
|
|
|
+ val newReference = newName.substringBefore(".")
|
|
|
+ val tmpContent = content.replace("""$oldName\b""".toRegex(), newReference)
|
|
|
+ if (content != tmpContent) {
|
|
|
+ content = tmpContent
|
|
|
+ isReplace = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ val tmpContent = content.replace("""\bR\.""".toRegex(), "$libNameSpace.R.")
|
|
|
+ if (content != tmpContent) {
|
|
|
+ content = tmpContent
|
|
|
+ isReplace = true
|
|
|
+ }
|
|
|
+ if (isReplace) {
|
|
|
+ Files.writeString(p0, content)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return FileVisitResult.CONTINUE
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+@Throws(ParserConfigurationException::class, IOException::class, SAXException::class)
|
|
|
+fun Path.replaceMainActivity(string: String): String {
|
|
|
+ val doc = DocumentBuilderFactory.newInstance()
|
|
|
+ .newDocumentBuilder()
|
|
|
+ .parse(this.inputStream())
|
|
|
+ doc.documentElement.normalize()
|
|
|
+ return doc.replace(string).asString()
|
|
|
+}
|
|
|
+
|
|
|
+fun Document.replace(string: String): Document {
|
|
|
+ val activity = this.getElementsByTagName("activity")
|
|
|
+ p@ for (i in 0..<activity.length) {
|
|
|
+ val element = activity.item(i) as Element
|
|
|
+ val intent = element.getElementsByTagName("intent-filter")
|
|
|
+ for (j in 0..<intent.length) {
|
|
|
+ val element1 = intent.item(j) as Element
|
|
|
+ val action = element1.getElementsByTagName("action")
|
|
|
+ for (k in 0..<action.length) {
|
|
|
+ val element2 = action.item(k) as Element
|
|
|
+ if (element2.getAttribute("android:name") == "android.intent.action.MAIN") {
|
|
|
+ element.setAttribute("android:name", string)
|
|
|
+ break@p
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this
|
|
|
+}
|
|
|
+
|
|
|
+fun Document.asString(): String {
|
|
|
+ val writer = StringWriter()
|
|
|
+ TransformerFactory.newInstance()
|
|
|
+ .newTransformer()
|
|
|
+ .transform(DOMSource(this), StreamResult(writer))
|
|
|
+ return writer.toString()
|
|
|
+}
|
|
|
+
|
|
|
+val renamedFiles = mutableMapOf<String, String>()
|
|
|
+
|
|
|
+fun Path.isResource(): Boolean {
|
|
|
+ if (this.fileName.name == "AndroidManifest.xml") return false
|
|
|
+ if (this.parent.name.contains("drawable|layout|mipmap".toRegex())) {
|
|
|
+ return this.fileName.extension.matches("xml|png|webp|jpeg|jpg|bitmap".toRegex())
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+fun Path.isSource(): Boolean {
|
|
|
+ return this.fileName.extension.matches("java|kt|xml".toRegex())
|
|
|
+}
|