From 6279236c5e40e115cc8d98808b09ea6026cde7e3 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 10 Aug 2025 20:27:55 -0400 Subject: [PATCH 1/5] feat: Magisk module building with gradle scripts --- app/build.gradle.kts | 50 +++++++++++++ app/magisk/customize.sh | 9 +++ app/magisk/module_installer.sh | 33 +++++++++ .../kotlin/im/angry/openeuicc/build/Magisk.kt | 74 +++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 app/magisk/customize.sh create mode 100644 app/magisk/module_installer.sh create mode 100644 buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 69e0db5..23410d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,4 @@ +import com.android.build.gradle.internal.api.ApkVariantOutputImpl import im.angry.openeuicc.build.* plugins { @@ -48,4 +49,53 @@ dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} + +val modulePropsTemplate = mutableMapOf( + "id" to android.defaultConfig.applicationId!!, + "name" to "OpenEUICC", + "version" to android.defaultConfig.versionName!!, + "versionCode" to "${android.defaultConfig.versionCode}", + "author" to "OpenEUICC authors", + "description" to "OpenEUICC is an open-source app that provides system-level eSIM integration." +) + +tasks.register("assembleDebugMagiskModuleDir") { + variant = "debug" + appName = "OpenEUICC" + permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") + moduleInstaller = project.file("magisk/module_installer.sh") + moduleCustomizeScript = project.file("magisk/customize.sh") + moduleProp = modulePropsTemplate.let { + it["description"] = "(debug build) ${it["description"]}" + it["versionCode"] = "${(android.applicationVariants.find { it.name == "debug" }!!.outputs.first() as ApkVariantOutputImpl).versionCodeOverride}" + it + } + dependsOn("assembleDebug") +} + +tasks.register("assembleDebugMagiskModule") { + dependsOn("assembleDebugMagiskModuleDir") + from((tasks.getByName("assembleDebugMagiskModuleDir") as MagiskModuleDirTask).outputDir) + archiveFileName = "magisk-debug.zip" + destinationDirectory = project.layout.buildDirectory.dir("magisk") + entryCompression = ZipEntryCompression.STORED +} + +tasks.register("assembleReleaseMagiskModuleDir") { + variant = "release" + appName = "OpenEUICC" + permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") + moduleInstaller = project.file("magisk/module_installer.sh") + moduleCustomizeScript = project.file("magisk/customize.sh") + moduleProp = modulePropsTemplate + dependsOn("assembleRelease") +} + +tasks.register("assembleReleaseMagiskModule") { + dependsOn("assembleReleaseMagiskModuleDir") + from((tasks.getByName("assembleReleaseMagiskModuleDir") as MagiskModuleDirTask).outputDir) + archiveFileName = "magisk-release.zip" + destinationDirectory = project.layout.buildDirectory.dir("magisk") + entryCompression = ZipEntryCompression.STORED } \ No newline at end of file diff --git a/app/magisk/customize.sh b/app/magisk/customize.sh new file mode 100644 index 0000000..d052558 --- /dev/null +++ b/app/magisk/customize.sh @@ -0,0 +1,9 @@ +TMP_FILE="$TMPDIR/{APK_NAME}" + +chmod u+x "$MODPATH/uninstall.sh" +cp "$MODPATH/{APK_PATH}" "$TMP_FILE" + +pm install -r "$TMP_FILE" +rm -f "$TMP_FILE" + +pm grant "{PKG_NAME}" android.permission.READ_PHONE_STATE \ No newline at end of file diff --git a/app/magisk/module_installer.sh b/app/magisk/module_installer.sh new file mode 100644 index 0000000..28b48e5 --- /dev/null +++ b/app/magisk/module_installer.sh @@ -0,0 +1,33 @@ +#!/sbin/sh + +################# +# Initialization +################# + +umask 022 + +# echo before loading util_functions +ui_print() { echo "$1"; } + +require_new_magisk() { + ui_print "*******************************" + ui_print " Please install Magisk v20.4+! " + ui_print "*******************************" + exit 1 +} + +######################### +# Load util_functions.sh +######################### + +OUTFD=$2 +ZIPFILE=$3 + +mount /data 2>/dev/null + +[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk + +install_module +exit 0 diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt new file mode 100644 index 0000000..cb19a57 --- /dev/null +++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt @@ -0,0 +1,74 @@ +package im.angry.openeuicc.build + +import org.gradle.api.DefaultTask +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class MagiskModuleDirTask : DefaultTask() { + @get:Input + abstract val variant : Property + + @get:Input + abstract val appName : Property + + @get:InputFile + abstract val permsFile : Property + + @get:InputFile + abstract val moduleInstaller : Property + + @get:InputFile + abstract val moduleCustomizeScript : Property + + @get:Input + abstract val moduleProp : MapProperty + + @InputDirectory + val inputDir = variant.map { project.layout.buildDirectory.dir("outputs/apk/${it}") } + + @OutputDirectory + val outputDir = variant.map { project.layout.buildDirectory.dir("magisk/${it}") } + + @TaskAction + fun build() { + val dir = outputDir.get().get() + project.mkdir(dir) + val systemExtDir = dir.dir("system/system_ext") + val permDir = dir.dir("system/system_ext/etc/permissions") + val appDir = systemExtDir.dir("priv-app/${appName.get()}") + val metaInfDir = dir.dir("META-INF/com/google/android") + project.mkdir(systemExtDir) + project.mkdir(metaInfDir) + project.mkdir(appDir) + project.mkdir(permDir) + project.copy { + into(appDir) + from(inputDir) { + include("app-${variant.get()}.apk") + rename("app-${variant.get()}.apk", "${appName.get()}.apk") + } + } + project.copy { + from(permsFile) + into(permDir) + } + project.copy { + from(moduleInstaller) + into(metaInfDir) + rename(".*", "update-binary") + } + project.copy { + from(moduleCustomizeScript) + into(dir) + rename(".*", "customize.sh") + } + metaInfDir.file("updater-script").asFile.writeText("# MAGISK") + dir.file("module.prop").asFile.writeText(moduleProp.get().map { (k, v) -> "$k=$v" }.joinToString("\n")) + } +} \ No newline at end of file From ff9fcbc291c1155505b30b734afd08a218c5e761 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 10 Aug 2025 22:09:17 -0400 Subject: [PATCH 2/5] Can we build magisk artifacts from CI? --- .forgejo/workflows/build-debug.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml index 660dabc..a72a3d0 100644 --- a/.forgejo/workflows/build-debug.yml +++ b/.forgejo/workflows/build-debug.yml @@ -33,14 +33,23 @@ jobs: uses: https://gitea.angry.im/actions/setup-android@v3 - name: Build Debug APKs - run: ./gradlew --no-daemon assembleDebug + run: ./gradlew --no-daemon assembleDebug :app:assembleDebugMagiskModuleDir - name: Copy Artifacts - run: find . -name 'app*-debug.apk' -exec cp {} . \; + steps: | + find . -name 'app*-debug.apk' -exec cp {} . \; + cp -r app/build/magisk/debug ./magisk-debug - - name: Upload Artifacts + - name: Upload APK Artifacts uses: https://gitea.angry.im/actions/upload-artifact@v3 with: name: Debug APKs compression-level: 0 path: app*-debug.apk + + - name: Upload Magisk Artifacts + uses: https://gitea.angry.im/actions/upload-artifact@v3 + with: + name: magisk-debug + compression-level: 0 + path: magisk-debug From 29e1b9c99193917e6a0b3cf47ffa5e09208c7400 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 10 Aug 2025 22:10:34 -0400 Subject: [PATCH 3/5] oof --- .forgejo/workflows/build-debug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml index a72a3d0..0818b8b 100644 --- a/.forgejo/workflows/build-debug.yml +++ b/.forgejo/workflows/build-debug.yml @@ -36,7 +36,7 @@ jobs: run: ./gradlew --no-daemon assembleDebug :app:assembleDebugMagiskModuleDir - name: Copy Artifacts - steps: | + run: | find . -name 'app*-debug.apk' -exec cp {} . \; cp -r app/build/magisk/debug ./magisk-debug From 7e53fe543ae24f8f4bdee7ee13bfdd976296858c Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 11 Aug 2025 08:17:39 -0400 Subject: [PATCH 4/5] Fix customize.sh template --- app/build.gradle.kts | 8 ++++++-- app/magisk/customize.sh | 2 +- .../src/main/kotlin/im/angry/openeuicc/build/Magisk.kt | 10 +++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 23410d7..d8c41ec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -60,12 +60,16 @@ val modulePropsTemplate = mutableMapOf( "description" to "OpenEUICC is an open-source app that provides system-level eSIM integration." ) +val moduleCustomizeScript = project.file("magisk/customize.sh").readText() + .replace("{APK_NAME}", "OpenEUICC") + .replace("{PKG_NAME}", android.defaultConfig.applicationId!!) + tasks.register("assembleDebugMagiskModuleDir") { variant = "debug" appName = "OpenEUICC" permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") moduleInstaller = project.file("magisk/module_installer.sh") - moduleCustomizeScript = project.file("magisk/customize.sh") + moduleCustomizeScriptText = moduleCustomizeScript moduleProp = modulePropsTemplate.let { it["description"] = "(debug build) ${it["description"]}" it["versionCode"] = "${(android.applicationVariants.find { it.name == "debug" }!!.outputs.first() as ApkVariantOutputImpl).versionCodeOverride}" @@ -87,7 +91,7 @@ tasks.register("assembleReleaseMagiskModuleDir") { appName = "OpenEUICC" permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") moduleInstaller = project.file("magisk/module_installer.sh") - moduleCustomizeScript = project.file("magisk/customize.sh") + moduleCustomizeScriptText = moduleCustomizeScript moduleProp = modulePropsTemplate dependsOn("assembleRelease") } diff --git a/app/magisk/customize.sh b/app/magisk/customize.sh index d052558..707b401 100644 --- a/app/magisk/customize.sh +++ b/app/magisk/customize.sh @@ -1,7 +1,7 @@ TMP_FILE="$TMPDIR/{APK_NAME}" chmod u+x "$MODPATH/uninstall.sh" -cp "$MODPATH/{APK_PATH}" "$TMP_FILE" +cp "$MODPATH/system/system_ext/{APK_NAME}/{APK_NAME}.apk" "$TMP_FILE" pm install -r "$TMP_FILE" rm -f "$TMP_FILE" diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt index cb19a57..1f03a48 100644 --- a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt +++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt @@ -23,8 +23,8 @@ abstract class MagiskModuleDirTask : DefaultTask() { @get:InputFile abstract val moduleInstaller : Property - @get:InputFile - abstract val moduleCustomizeScript : Property + @get:Input + abstract val moduleCustomizeScriptText : Property @get:Input abstract val moduleProp : MapProperty @@ -63,11 +63,7 @@ abstract class MagiskModuleDirTask : DefaultTask() { into(metaInfDir) rename(".*", "update-binary") } - project.copy { - from(moduleCustomizeScript) - into(dir) - rename(".*", "customize.sh") - } + dir.file("customize.sh").asFile.writeText(moduleCustomizeScriptText.get()) metaInfDir.file("updater-script").asFile.writeText("# MAGISK") dir.file("module.prop").asFile.writeText(moduleProp.get().map { (k, v) -> "$k=$v" }.joinToString("\n")) } From d701276db29d6c859ac6c0aece4bb568d80e77f6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 11 Aug 2025 08:22:04 -0400 Subject: [PATCH 5/5] Add Magisk uninstall script --- app/build.gradle.kts | 5 +++++ app/magisk/uninstall.sh | 1 + buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 app/magisk/uninstall.sh diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d8c41ec..ddf92af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -64,12 +64,16 @@ val moduleCustomizeScript = project.file("magisk/customize.sh").readText() .replace("{APK_NAME}", "OpenEUICC") .replace("{PKG_NAME}", android.defaultConfig.applicationId!!) +val moduleUninstallScript = project.file("magisk/uninstall.sh").readText() + .replace("{PKG_NAME}", android.defaultConfig.applicationId!!) + tasks.register("assembleDebugMagiskModuleDir") { variant = "debug" appName = "OpenEUICC" permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") moduleInstaller = project.file("magisk/module_installer.sh") moduleCustomizeScriptText = moduleCustomizeScript + moduleUninstallScriptText = moduleUninstallScript moduleProp = modulePropsTemplate.let { it["description"] = "(debug build) ${it["description"]}" it["versionCode"] = "${(android.applicationVariants.find { it.name == "debug" }!!.outputs.first() as ApkVariantOutputImpl).versionCodeOverride}" @@ -92,6 +96,7 @@ tasks.register("assembleReleaseMagiskModuleDir") { permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml") moduleInstaller = project.file("magisk/module_installer.sh") moduleCustomizeScriptText = moduleCustomizeScript + moduleUninstallScriptText = moduleUninstallScript moduleProp = modulePropsTemplate dependsOn("assembleRelease") } diff --git a/app/magisk/uninstall.sh b/app/magisk/uninstall.sh new file mode 100644 index 0000000..1eb0200 --- /dev/null +++ b/app/magisk/uninstall.sh @@ -0,0 +1 @@ +pm uninstall "{PKG_NAME}" \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt index 1f03a48..6245d8c 100644 --- a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt +++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt @@ -26,6 +26,9 @@ abstract class MagiskModuleDirTask : DefaultTask() { @get:Input abstract val moduleCustomizeScriptText : Property + @get:Input + abstract val moduleUninstallScriptText : Property + @get:Input abstract val moduleProp : MapProperty @@ -64,6 +67,7 @@ abstract class MagiskModuleDirTask : DefaultTask() { rename(".*", "update-binary") } dir.file("customize.sh").asFile.writeText(moduleCustomizeScriptText.get()) + dir.file("uninstall.sh").asFile.writeText(moduleUninstallScriptText.get()) metaInfDir.file("updater-script").asFile.writeText("# MAGISK") dir.file("module.prop").asFile.writeText(moduleProp.get().map { (k, v) -> "$k=$v" }.joinToString("\n")) }