From 2269f60b25ffac77dd3a053fde01c927a15919b5 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 17 Oct 2022 11:45:06 +0300 Subject: [PATCH 1/5] Introduce utbot-android-studio module --- gradle.properties | 2 +- settings.gradle | 1 + ...UtAndroidGradleJavaProjectModelModifier.kt | 140 ++++++++++++++++++ utbot-intellij/build.gradle.kts | 2 + .../src/main/resources/META-INF/plugin.xml | 1 + 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt diff --git a/gradle.properties b/gradle.properties index 8dc2f9fdd2..bf28daded5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ ideType=IC # In order to run Android Studion instead of Intellij Community, # specify the path to your Android Studio installation -//androidStudioPath=D:/AS2021 +androidStudioPath=D:/AS2021 pythonCommunityPluginVersion=222.4167.37 #Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions diff --git a/settings.gradle b/settings.gradle index 7e7cd525dc..7375c1636b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,4 +30,5 @@ include 'utbot-maven' include 'utbot-summary-tests' include 'utbot-framework-test' include 'utbot-rd' +include 'utbot-android-studio' diff --git a/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt new file mode 100644 index 0000000000..c9d266e44c --- /dev/null +++ b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt @@ -0,0 +1,140 @@ +package org.androidstudio.plugin.util + +import com.android.tools.idea.gradle.AndroidGradleJavaProjectModelModifier +import com.android.tools.idea.gradle.dsl.api.GradleBuildModel +import com.android.tools.idea.gradle.dsl.api.dependencies.ArtifactDependencySpec +import com.android.tools.idea.gradle.dsl.api.dependencies.CommonConfigurationNames +import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker +import com.android.tools.idea.gradle.project.sync.GradleSyncListener +import com.android.tools.idea.gradle.project.sync.idea.GradleSyncExecutor +import com.android.tools.idea.gradle.util.GradleUtil +import com.android.tools.idea.project.AndroidProjectInfo +import com.android.tools.idea.projectsystem.TestArtifactSearchScopes +import com.google.wireless.android.sdk.stats.GradleSyncStats +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.command.undo.BasicUndoableAction +import com.intellij.openapi.command.undo.UndoManager +import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.concurrency.AsyncPromise +import org.jetbrains.concurrency.Promise + +class UtAndroidGradleJavaProjectModelModifier : AndroidGradleJavaProjectModelModifier() { + override fun addExternalLibraryDependency( + modules: Collection, + descriptor: ExternalLibraryDescriptor, + scope: DependencyScope + ): Promise? { + val module = ContainerUtil.getFirstItem(modules) ?: return null + + if (!isAndroidGradleProject(module.project)) { + return null + } + + val dependencySpec = ArtifactDependencySpec.create(descriptor.libraryArtifactId, descriptor.libraryGroupId, descriptor.preferredVersion) + return addExternalLibraryDependency(module, dependencySpec, scope) + + } + + // returns single external project path if it is the same for all the modules, or null + private fun getSingleExternalProjectPathOrNull(modules: Collection): String? { + var projectPath: String? = null + for (module in modules) { + val rootProjectPathForModule = getSingleExternalProjectPathOrNull(module) + ?: return null + if (projectPath == null) { + projectPath = rootProjectPathForModule + } else if (projectPath != rootProjectPathForModule) { + return null + } + } + return projectPath + } + + private fun getSingleExternalProjectPathOrNull(module: Module): String? = + ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath() + + private fun addExternalLibraryDependency( + module: Module, + dependencySpec: ArtifactDependencySpec, + scope: DependencyScope, + ): Promise? { + val project = module.project + val openedFile = FileEditorManagerEx.getInstanceEx(project).currentFile + val buildModelsToUpdate: MutableList = ArrayList() + + val buildModel = GradleBuildModel.get(module) ?: return null + val configurationName = getConfigurationName(module, scope, openedFile) + val dependencies = buildModel.dependencies() + dependencies.addArtifact(configurationName, dependencySpec) + buildModelsToUpdate.add(buildModel) + + WriteCommandAction.writeCommandAction(project).withName("Add Gradle Library Dependency").run { + buildModelsToUpdate.forEach { buildModel -> buildModel.applyChanges() } + registerUndoAction(project) + } + + return doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ADD_LIBRARY_DEPENDENCY) + } + + private fun getConfigurationName(module: Module, scope: DependencyScope, openedFile: VirtualFile?): String = + GradleUtil.mapConfigurationName( + getLegacyConfigurationName(module, scope, openedFile), + GradleUtil.getAndroidGradleModelVersionInUse(module), + false + ) + + private fun getLegacyConfigurationName( + module: Module, + scope: DependencyScope, + openedFile: VirtualFile? + ): String { + if (!scope.isForProductionCompile) { + val testScopes = TestArtifactSearchScopes.getInstance(module) + if (testScopes != null && openedFile != null) { + return if (testScopes.isAndroidTestSource(openedFile)) CommonConfigurationNames.ANDROID_TEST_COMPILE else CommonConfigurationNames.TEST_COMPILE + } + } + return CommonConfigurationNames.COMPILE + } + + private fun registerUndoAction(project: Project) { + UndoManager.getInstance(project).undoableActionPerformed(object : BasicUndoableAction() { + + override fun undo() { + doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ACTION_UNDONE) + + } + + override fun redo() { + doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ACTION_REDONE) + } + }) + } + + private fun isAndroidGradleProject(project: Project): Boolean = + AndroidProjectInfo.getInstance(project).requiresAndroidModel() + + private fun doAndroidGradleSync(project: Project, trigger: GradleSyncStats.Trigger): AsyncPromise { + val promise = AsyncPromise() + val request = GradleSyncInvoker.Request(trigger) + val listener = object : GradleSyncListener { + override fun syncSucceeded(project: Project) { + promise.setResult(null) + } + + override fun syncFailed(project: Project, errorMessage: String) { + promise.setError(errorMessage) + } + } + GradleSyncExecutor(project).sync(request, listener) + + return promise + } +} \ No newline at end of file diff --git a/utbot-intellij/build.gradle.kts b/utbot-intellij/build.gradle.kts index 16dd9156ef..b8388b9c74 100644 --- a/utbot-intellij/build.gradle.kts +++ b/utbot-intellij/build.gradle.kts @@ -96,4 +96,6 @@ dependencies { //api(project(":utbot-analytics")) testImplementation("org.mock-server:mockserver-netty:5.4.1") testApi(project(":utbot-framework")) + + implementation(project(":utbot-android-studio")) } \ No newline at end of file diff --git a/utbot-intellij/src/main/resources/META-INF/plugin.xml b/utbot-intellij/src/main/resources/META-INF/plugin.xml index 81f3409165..e8e867ab10 100644 --- a/utbot-intellij/src/main/resources/META-INF/plugin.xml +++ b/utbot-intellij/src/main/resources/META-INF/plugin.xml @@ -32,6 +32,7 @@ + From bc7a84571ab2df2c26b91968097ef4e32e0fbb02 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 19 Oct 2022 02:10:43 +0300 Subject: [PATCH 2/5] Remove unused method --- ...UtAndroidGradleJavaProjectModelModifier.kt | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt index c9d266e44c..6ff259f3cb 100644 --- a/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt +++ b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt @@ -42,24 +42,6 @@ class UtAndroidGradleJavaProjectModelModifier : AndroidGradleJavaProjectModelMod } - // returns single external project path if it is the same for all the modules, or null - private fun getSingleExternalProjectPathOrNull(modules: Collection): String? { - var projectPath: String? = null - for (module in modules) { - val rootProjectPathForModule = getSingleExternalProjectPathOrNull(module) - ?: return null - if (projectPath == null) { - projectPath = rootProjectPathForModule - } else if (projectPath != rootProjectPathForModule) { - return null - } - } - return projectPath - } - - private fun getSingleExternalProjectPathOrNull(module: Module): String? = - ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath() - private fun addExternalLibraryDependency( module: Module, dependencySpec: ArtifactDependencySpec, @@ -118,8 +100,7 @@ class UtAndroidGradleJavaProjectModelModifier : AndroidGradleJavaProjectModelMod }) } - private fun isAndroidGradleProject(project: Project): Boolean = - AndroidProjectInfo.getInstance(project).requiresAndroidModel() + private fun isAndroidGradleProject(project: Project): Boolean = AndroidProjectInfo.getInstance(project).requiresAndroidModel() private fun doAndroidGradleSync(project: Project, trigger: GradleSyncStats.Trigger): AsyncPromise { val promise = AsyncPromise() From 4674104fe037e84ea932b3b8bf26228c0d6f8d6d Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 19 Oct 2022 03:01:38 +0300 Subject: [PATCH 3/5] Path corrected for non Android Studio users --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bf28daded5..011d320a67 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ ideType=IC # In order to run Android Studion instead of Intellij Community, # specify the path to your Android Studio installation -androidStudioPath=D:/AS2021 +//androidStudioPath=your_path_to_android_studio pythonCommunityPluginVersion=222.4167.37 #Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions From 23b5e91737e2ffbbb91cd006b8f5ba6a52db2cf7 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 19 Oct 2022 03:05:06 +0300 Subject: [PATCH 4/5] Missed file --- utbot-android-studio/build.gradle.kts | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 utbot-android-studio/build.gradle.kts diff --git a/utbot-android-studio/build.gradle.kts b/utbot-android-studio/build.gradle.kts new file mode 100644 index 0000000000..c44f8cf363 --- /dev/null +++ b/utbot-android-studio/build.gradle.kts @@ -0,0 +1,42 @@ +val ideType: String? by rootProject +val androidStudioPath: String? by rootProject + +plugins { + id("org.jetbrains.intellij") version "1.7.0" +} + +project.tasks.asMap["runIde"]?.enabled = false + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "11" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = listOf( + "java", + "org.jetbrains.kotlin:212-1.7.10-release-333-AS5457.46" + ) + + plugins.set(jvmPlugins + androidPlugins) + + version.set("212.5712.43") + type.set(ideTypeOrAndroidStudio) +} \ No newline at end of file From 83ed70c615e1591750595175d374902946f4eea3 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 19 Oct 2022 10:52:53 +0300 Subject: [PATCH 5/5] Add documentation to the gradle script --- utbot-android-studio/build.gradle.kts | 48 +++++++++++++++------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/utbot-android-studio/build.gradle.kts b/utbot-android-studio/build.gradle.kts index c44f8cf363..13cefccbba 100644 --- a/utbot-android-studio/build.gradle.kts +++ b/utbot-android-studio/build.gradle.kts @@ -1,14 +1,34 @@ -val ideType: String? by rootProject -val androidStudioPath: String? by rootProject - plugins { id("org.jetbrains.intellij") version "1.7.0" } -project.tasks.asMap["runIde"]?.enabled = false +intellij { + /* + The list of Android Studio releases can be found here https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html + For each release a compatible Intellij Idea version can be found in the right column. Specify it in "version.set("...") + + NOTE!!! + We use Android Studio Chipmunk (2021.2.1), although Android Studio Dolphin (2021.3.1) has been released. + The reason is that a version of Kotlin plugin compatible with Android Studio is required. + The list of Kotlin plugin releases can be found here https://plugins.jetbrains.com/plugin/6954-kotlin/versions/stable + The last compatible with AS plugin version on 19 Oct 2022 is Kotlin 212-1.7.10-release-333-AS5457.46, + it is not compatible with Dolphin release (https://plugins.jetbrains.com/plugin/6954-kotlin/versions/stable/193255). + */ + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = listOf( + "java", + "org.jetbrains.kotlin:212-1.7.10-release-333-AS5457.46" + ) + + plugins.set(jvmPlugins + androidPlugins) -// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file -val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + version.set("212.5712.43") + type.set("IC") +} + +project.tasks.asMap["runIde"]?.enabled = false tasks { compileKotlin { @@ -23,20 +43,4 @@ tasks { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } -} - - -intellij { - - val androidPlugins = listOf("org.jetbrains.android") - - val jvmPlugins = listOf( - "java", - "org.jetbrains.kotlin:212-1.7.10-release-333-AS5457.46" - ) - - plugins.set(jvmPlugins + androidPlugins) - - version.set("212.5712.43") - type.set(ideTypeOrAndroidStudio) } \ No newline at end of file