From 8f74b0faee179fe510a5aeb239a06bb493b31d4c Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sun, 6 Aug 2023 20:46:30 +0530 Subject: [PATCH] fix: project initialization error is not handled properly --- .../editor/ProjectHandlerActivity.kt | 34 +++++++++---- .../impl/ProjectInitializationExeption.kt | 26 ++++++++++ .../tooling/impl/ToolingApiServerImpl.kt | 51 ++++++++++--------- .../api/messages/result/InitializeResult.kt | 8 +-- 4 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ProjectInitializationExeption.kt diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt index 6edfc367e..8716abcee 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt @@ -80,9 +80,12 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { protected val mBuildEventListener = EditorBuildEventListener() companion object { + const val STATE_KEY_FROM_SAVED_INSTANACE = "ide.editor.isFromSavedInstance" const val STATE_KEY_SHOULD_INITIALIZE = "ide.editor.isInitializing" - @JvmStatic private val buildServiceConnection = GradleBuildServiceConnnection() + + @JvmStatic + private val buildServiceConnection = GradleBuildServiceConnnection() } abstract fun doCloseAll(runAfter: () -> Unit) @@ -258,9 +261,11 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { this.initializingFuture!!.whenCompleteAsync { result, error -> releaseServerListener() - if (result == null || error != null) { + if (result == null || !result.isSuccessful || error != null) { log.error("An error occurred initializing the project with Tooling API", error) - setStatus(getString(string.msg_project_initialization_failed)) + ThreadUtils.runOnUiThread { + postProjectInit(false) + } return@whenCompleteAsync } @@ -306,7 +311,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { cachedInitResult = result setupProject() notifyProjectUpdate() - ThreadUtils.runOnUiThread { postProjectInit() } + ThreadUtils.runOnUiThread { postProjectInit(true) } } protected open fun preProjectInit() { @@ -314,7 +319,15 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { viewModel.isInitializing = true } - protected open fun postProjectInit() { + protected open fun postProjectInit(isSuccessful: Boolean) { + if (!isSuccessful) { + setStatus(getString(string.msg_project_initialization_failed)) + flashError(string.msg_project_initialization_failed) + viewModel.isInitializing = false + projectInitialized = false + return + } + initialSetup() setStatus(getString(string.msg_project_initialized)) viewModel.isInitializing = false @@ -336,7 +349,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { val moduleDirs = try { - rootProject!!.subProjects.stream().map(GradleProject::projectDir).collect(Collectors.toList()) + rootProject!!.subProjects.stream().map(GradleProject::projectDir) + .collect(Collectors.toList()) } catch (e: Throwable) { flashError(getString(string.msg_no_modules)) emptyList() @@ -392,10 +406,10 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { if (extensions.isNotEmpty()) { if (extensions.contains("|")) { for (str in - extensions - .split(Pattern.quote("|").toRegex()) - .dropLastWhile { it.isEmpty() } - .toTypedArray()) { + extensions + .split(Pattern.quote("|").toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray()) { if (str.trim().isEmpty()) { continue } diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ProjectInitializationExeption.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ProjectInitializationExeption.kt new file mode 100644 index 000000000..29aa55a67 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ProjectInitializationExeption.kt @@ -0,0 +1,26 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.tooling.impl + +/** + * Exception thrown when the project fails to sync/initialize. + * + * @author Akash Yadav + */ +class ProjectInitializationExeption(reason: String = "", cause: Throwable? = null) : + RuntimeException("Failed to initialize project: $reason", cause) \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt index 170bd08bd..7a43367d3 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt @@ -43,7 +43,6 @@ import com.itsaky.androidide.tooling.impl.sync.ModelBuilderException import com.itsaky.androidide.tooling.impl.sync.RootModelBuilder import com.itsaky.androidide.tooling.impl.util.StopWatch import com.itsaky.androidide.utils.ILogger -import org.eclipse.lsp4j.jsonrpc.CompletableFutures import org.gradle.tooling.BuildCancelledException import org.gradle.tooling.BuildException import org.gradle.tooling.CancellationTokenSource @@ -71,7 +70,7 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : private val log = ILogger.newInstance(javaClass.simpleName) override fun initialize(params: InitializeProjectMessage): CompletableFuture { - return CompletableFutures.computeAsync { + return CompletableFuture.supplyAsync { try { if (initialized && connector != null) { connector?.disconnect() @@ -91,11 +90,8 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : stopWatch.lap("Connector created") if (this.connector == null) { - throw CompletionException( - RuntimeException( - "Unable to create gradle connector for project directory: ${params.directory}" - ) - ) + throw IllegalStateException( + "Unable to create gradle connector for project directory: ${params.directory}") } notifyBeforeBuild(BuildInfo(emptyList())) @@ -103,11 +99,17 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : val connection = this.connector!!.connect() stopWatch.lapFromLast("Project connection established") - val project = RootModelBuilder.build(connection to "debug") as? ProjectImpl? - project ?: throw ModelBuilderException("Failed to build project model") - stopWatch.lapFromLast("Project read successful") + val project = try { + val impl = RootModelBuilder.build(connection to "debug") as? ProjectImpl? + ?: throw ModelBuilderException("Failed to build project model") + impl + } catch (err: Throwable) { + throw err + } finally { + connection.close() + } - connection.close() + stopWatch.lapFromLast("Project read successful") stopWatch.log() this.project.rootProject = project.rootProject @@ -115,13 +117,12 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : initialized = true notifyBuildSuccess(emptyList()) - return@computeAsync InitializeResult(emptyMap()) + return@supplyAsync InitializeResult(true) } catch (err: Throwable) { - log.error(err) + log.error("Failed to initialize project", err) notifyBuildFailure(emptyList()) + return@supplyAsync InitializeResult(false) } - - return@computeAsync InitializeResult(emptyMap()) } } @@ -130,14 +131,14 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : } override fun getRootProject(): CompletableFuture { - return CompletableFutures.computeAsync { + return CompletableFuture.supplyAsync { assertProjectInitialized() - return@computeAsync this.project + return@supplyAsync this.project } } override fun executeTasks(message: TaskExecutionMessage): CompletableFuture { - return CompletableFutures.computeAsync { + return CompletableFuture.supplyAsync { assertProjectInitialized() log.debug("Received request to run tasks.", message) @@ -177,10 +178,10 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : builder.run() this.buildCancellationToken = null notifyBuildSuccess(message.tasks) - return@computeAsync TaskExecutionResult(true, null) + return@supplyAsync TaskExecutionResult(true, null) } catch (error: Throwable) { notifyBuildFailure(message.tasks) - return@computeAsync TaskExecutionResult(false, getTaskFailureType(error)) + return@supplyAsync TaskExecutionResult(false, getTaskFailureType(error)) } } } @@ -222,9 +223,9 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : } override fun cancelCurrentBuild(): CompletableFuture { - return CompletableFutures.computeAsync { + return CompletableFuture.supplyAsync { if (this.buildCancellationToken == null) { - return@computeAsync BuildCancellationRequestResult( + return@supplyAsync BuildCancellationRequestResult( false, BuildCancellationRequestResult.Reason.NO_RUNNING_BUILD ) @@ -235,10 +236,10 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : } catch (e: Exception) { val failureReason = CANCELLATION_ERROR failureReason.message = "${failureReason.message}: ${e.message}" - return@computeAsync BuildCancellationRequestResult(false, failureReason) + return@supplyAsync BuildCancellationRequestResult(false, failureReason) } - return@computeAsync BuildCancellationRequestResult(true, null) + return@supplyAsync BuildCancellationRequestResult(true, null) } } @@ -270,7 +271,7 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : } private fun assertProjectInitialized() { - if (!isServerInitialized().get() || project == null) { + if (!isServerInitialized().get()) { throw CompletionException(IllegalStateException("Project is not initialized!")) } } diff --git a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt index 760e19c42..874c75980 100644 --- a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt +++ b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/result/InitializeResult.kt @@ -17,14 +17,10 @@ package com.itsaky.androidide.tooling.api.messages.result -import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues - /** * Result received after an initialize project request. * - * @param project The initialized project model. - * @param syncIssues The issues reported by the Android Gradle Plugin during the project sync. - * + * @param isSuccessful Whether the project initialization was successful. * @author Akash Yadav */ -data class InitializeResult(val syncIssues: Map) +class InitializeResult(val isSuccessful: Boolean)