From 8a5d21e7730ce1b1089b6f754bf750f3f390d28c Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 16 May 2023 23:01:16 +0530 Subject: [PATCH] chore(ci): build android template projects --- .github/workflows/build.yml | 46 ++++++++++++ .tools/download_key.sh | 0 .../tooling/testing/ToolingApiTestLauncher.kt | 73 ++++++++----------- templates-impl/build.gradle.kts | 1 + .../templates/TemplateBuilderTest.kt | 4 +- .../com/itsaky/androidide/templates/utils.kt | 68 +++++++++++++++-- 6 files changed, 140 insertions(+), 52 deletions(-) mode change 100644 => 100755 .tools/download_key.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e1f6bd2c..98ae06825 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,6 +124,52 @@ jobs: name: test-reports path: '**/build/reports/tests/' + build_templates: + name: Build templates + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Change Gradle wrapper permissions + run: chmod +x ./gradlew + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Build debug templates + run: ./gradlew :templates-impl:testDebug --continue + env: + ANDROIDIDE_TEST: true + ANDROIDIDE_TEMPLATES_BUILD: true + IDE_SIGNING_ALIAS: ${{ secrets.IDE_SIGNING_ALIAS }} + IDE_SIGNING_AUTH_PASS: ${{ secrets.IDE_SIGNING_AUTH_PASS }} + IDE_SIGNING_AUTH_USER: ${{ secrets.IDE_SIGNING_AUTH_USER }} + IDE_SIGNING_KEY_PASS: ${{ secrets.IDE_SIGNING_KEY_PASS }} + IDE_SIGNING_STORE_PASS: ${{ secrets.IDE_SIGNING_STORE_PASS }} + IDE_SIGNING_URL: ${{ secrets.IDE_SIGNING_URL }} + - name: Upload test reports + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: templates-test-reports + path: 'templates-impl/build/reports/tests/' + - name: Upload template APKs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: templates-apks + path: 'build/templates/' + test_tooling_api: name: Test Tooling API implementation runs-on: ubuntu-latest diff --git a/.tools/download_key.sh b/.tools/download_key.sh old mode 100644 new mode 100755 diff --git a/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt b/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt index b09bb029f..164d18389 100644 --- a/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt +++ b/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt @@ -43,33 +43,26 @@ import kotlin.io.path.pathString * * ***For testing purposes only!*** * - * ***This must be used only from modules that are located in the 'build-tools' directory!*** - * * @author Akash Yadav */ class ToolingApiTestLauncher { private val opens = - mutableMapOf("java.base" to "java.lang", "java.base" to "java.util", "java.base" to "java.io") + mutableMapOf("java.base" to "java.lang", "java.base" to "java.util", + "java.base" to "java.io") private val exports = - mutableMapOf( - "jdk.compiler" to "com.sun.tools.javac.api", + mutableMapOf("jdk.compiler" to "com.sun.tools.javac.api", "jdk.compiler" to "com.sun.tools.javac.file", "jdk.compiler" to "com.sun.tools.javac.parser", "jdk.compiler" to "com.sun.tools.javac.tree", - "jdk.compiler" to "com.sun.tools.javac.util" - ) + "jdk.compiler" to "com.sun.tools.javac.util") fun launchServer( client: IToolingApiClient = MultiVersionTestClient(), ): Pair { - val builder = - ProcessBuilder( - createProcessCmd( - FileProvider.implModule().resolve("build/libs/tooling-api-all.jar").pathString - ) - ) + val builder = ProcessBuilder(createProcessCmd(FileProvider.implModule() + .resolve("build/libs/tooling-api-all.jar").pathString)) val androidHome = findAndroidHome() println("ANDROID_HOME=$androidHome") builder.environment()["ANDROID_SDK_ROOT"] = androidHome @@ -77,7 +70,9 @@ class ToolingApiTestLauncher { val proc = builder.start() Thread(Reader(proc.errorStream)).start() - val launcher = ToolingApiLauncher.newClientLauncher(client, proc.inputStream, proc.outputStream) + val launcher = + ToolingApiLauncher.newClientLauncher(client, proc.inputStream, + proc.outputStream) launcher.startListening() @@ -96,7 +91,8 @@ class ToolingApiTestLauncher { Collections.addAll(cmd, "-jar", jar) - println("[ToolingApiTestLauncher] Java cmd: " + cmd.joinToString(separator = " ")) + println( + "[ToolingApiTestLauncher] Java cmd: " + cmd.joinToString(separator = " ")) return cmd } @@ -108,6 +104,7 @@ class ToolingApiTestLauncher { ) : IToolingApiClient { companion object { + const val buildFile = "build.gradle" const val buildFileIn = "$buildFile.in" @@ -117,10 +114,12 @@ class ToolingApiTestLauncher { const val DEFAULT_AGP_VERSION = "7.2.0" const val DEFAULT_GRADLE_VERSION = "7.5.1" - const val GENERATED_FILE_WARNING = "DO NOT EDIT - Automatically generated file" + const val GENERATED_FILE_WARNING = + "DO NOT EDIT - Automatically generated file" private val log = ILogger.newInstance("MultiVersionTestClient") } + override fun logMessage(line: LogLine) { log.log(line.priority, line.formattedTagAndMessage()) } @@ -135,20 +134,13 @@ class ToolingApiTestLauncher { log.debug("Gradle Version : ${this.gradleVersion}") log.debug("-----------------------------------") - projectDir - .resolve(buildFileIn) - .replaceContents( - dest = projectDir.resolve(buildFile), - candidate = "@@TOOLING_API_TEST_AGP_VERSION@@" to this.agpVersion - ) + projectDir.resolve(buildFileIn) + .replaceContents(dest = projectDir.resolve(buildFile), + candidate = "@@TOOLING_API_TEST_AGP_VERSION@@" to this.agpVersion) - projectDir - .resolve(gradlewPropsIn) - .replaceContents( - comment = "#", - dest = projectDir.resolve(gradlewProps), - candidate = "@@GRADLE_VERSION@@" to this.gradleVersion - ) + projectDir.resolve(gradlewPropsIn) + .replaceContents(comment = "#", dest = projectDir.resolve(gradlewProps), + candidate = "@@GRADLE_VERSION@@" to this.gradleVersion) } override fun onBuildSuccessful(result: BuildResult) {} @@ -163,23 +155,17 @@ class ToolingApiTestLauncher { override fun checkGradleWrapperAvailability(): CompletableFuture = CompletableFuture.completedFuture(GradleWrapperCheckResult(true)) - private fun Path.replaceContents( - dest: Path, - comment: String = "//", - candidate: Pair + private fun Path.replaceContents(dest: Path, comment: String = "//", + candidate: Pair ) = replaceContents(dest, comment, *arrayOf(candidate)) - private fun Path.replaceContents( - dest: Path, - comment: String = "//", - vararg candidates: Pair + private fun Path.replaceContents(dest: Path, comment: String = "//", + vararg candidates: Pair ) { - val contents = - StringBuilder() - .append(comment) - .append(" ") - .append(GENERATED_FILE_WARNING) - .append(System.getProperty("line.separator").repeat(2)) + val contents = StringBuilder().append(comment) + .append(" ") + .append(GENERATED_FILE_WARNING) + .append(System.getProperty("line.separator").repeat(2)) bufferedReader().use { reader -> reader.readText().also { text -> @@ -201,6 +187,7 @@ class ToolingApiTestLauncher { } private class Reader(val input: InputStream) : Runnable { + private val log = ILogger.newInstance(javaClass.simpleName) override fun run() { try { diff --git a/templates-impl/build.gradle.kts b/templates-impl/build.gradle.kts index 955c2cb0a..7e38ce2ad 100644 --- a/templates-impl/build.gradle.kts +++ b/templates-impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { testImplementation(projects.lsp.api) testImplementation(projects.preferences) testImplementation(projects.shared) + testImplementation(projects.subprojects.toolingApiTesting) testImplementation(libs.tests.androidx.test.core) testImplementation(libs.tests.google.truth) testImplementation(libs.tests.junit) diff --git a/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt b/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt index f5235f0a3..13f3128d3 100644 --- a/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt +++ b/templates-impl/src/test/java/com/itsaky/androidide/templates/TemplateBuilderTest.kt @@ -47,7 +47,7 @@ class TemplateBuilderTest { @Test fun `root project generator test`() { - val template = testTemplate("Root") { + val template = testTemplate("Root", generate = false) { baseProject { } } @@ -91,7 +91,7 @@ class TemplateBuilderTest { @Test fun `test project with module`() { - testTemplate("Test") { + testTemplate("Test", generate = false) { baseProject { defaultAppModule { diff --git a/templates-impl/src/test/java/com/itsaky/androidide/templates/utils.kt b/templates-impl/src/test/java/com/itsaky/androidide/templates/utils.kt index 6993a307c..a246c674a 100644 --- a/templates-impl/src/test/java/com/itsaky/androidide/templates/utils.kt +++ b/templates-impl/src/test/java/com/itsaky/androidide/templates/utils.kt @@ -24,6 +24,7 @@ import com.itsaky.androidide.preferences.internal.prefManager import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder import com.itsaky.androidide.templates.base.ModuleTemplateBuilder import com.itsaky.androidide.templates.base.ProjectTemplateBuilder +import com.itsaky.androidide.tooling.testing.findAndroidHome import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.FileProvider import io.mockk.every @@ -37,6 +38,10 @@ val testProjectsDir: File by lazy { FileProvider.currentDir().resolve("build/templateTest").toFile() } +val isBuildTemplates by lazy { + System.getenv("ANDROIDIDE_TEMPLATES_BUILD").toBoolean() +} + fun mockPrefManager(configure: PreferenceManager.() -> Unit = {}) { mockkStatic(::prefManager) @@ -88,7 +93,8 @@ private fun mockConstructors(klass: KClass, configure() } -fun testTemplate(name: String, builder: () -> Template): Template { +fun testTemplate(name: String, generate: Boolean = true, builder: () -> Template +): Template { mockPrefManager() mockTemplateBuilders() testProjectsDir.apply { @@ -102,6 +108,15 @@ fun testTemplate(name: String, builder: () -> Template): Template { val template = builder() + if (generate) { + generateTemplateProject(name, template) + } + + return template +} + +private fun generateTemplateProject(name: String, template: Template +) { for (language in Language.values()) { val packageName = "com.itsaky.androidide.template.${language.lang}" run { @@ -111,7 +126,7 @@ fun testTemplate(name: String, builder: () -> Template): Template { mockTemplateDatas(useKts = false) template.setupRootProjectParams(name = projectName, packageName = packageName, language = language) - template.executeRecipe() + template.executeRecipe(projectName) unmockTemplateDatas() } @@ -121,12 +136,10 @@ fun testTemplate(name: String, builder: () -> Template): Template { mockTemplateDatas(useKts = true) template.setupRootProjectParams(name = projectName, packageName = "${packageName}.kts", language = language) - template.executeRecipe() + template.executeRecipe(projectName) unmockTemplateDatas() } } - - return template } fun Template.setupRootProjectParams(name: String = "TestTemplate", @@ -157,8 +170,35 @@ fun Template.setupRootProjectParams(name: String = "TestTemplate", (param as EnumParameter).value = minSdk } -fun Template.executeRecipe() { +fun Template.executeRecipe(projectName: String) { TestRecipeExecutor().apply(recipe) + + if (isBuildTemplates) { + val projectDir = File(testProjectsDir, projectName) + if (!projectDir.isDirectory) { + return + } + + var name = "gradlew" + if (isWindows()) { + name += ".bat" + } + + val gradlew = File(projectDir, name) + val process = wrapperProcess(gradlew) + + // Wait for the build process to finish + check( + process.waitFor() == 0) { "Failed to build template project '$projectName' at location '$projectDir'" } + + // if the build process is successful, copy the apk to a common location + val apk = File(projectDir, "app/build/outputs/apk/debug/app-debug.apk") + val dest = FileProvider.projectRoot() + .resolve("build/templates/$projectName.apk") + .toFile() + dest.parentFile!!.mkdirs() + apk.copyTo(dest) + } } fun Collection>.assertParameterTypes( @@ -174,4 +214,18 @@ fun Collection.assertTypes(checker: (Int) -> KClass) { forEachIndexed { index, element -> assertThat(element).isInstanceOf(checker(index).java) } -} \ No newline at end of file +} + +private fun wrapperProcess(gradlew: File) = ProcessBuilder(cmd(gradlew)).apply { + environment().apply { + put("JAVA_HOME", System.getProperty("java.home")) + put("ANDROID_HOME", findAndroidHome()) + } +}.start() + +private fun cmd(gradlew: File) = + if (isWindows()) listOf("cmd", "/c", gradlew.absolutePath, "assembleDebug") + else listOf("bash", gradlew.absolutePath, "assembleDebug") + +private fun isWindows() = + System.getProperty("os.name")?.contains("windows", ignoreCase = true) == true \ No newline at end of file