Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filesystem refresh after dependencies fetch should happen outside the read action #245

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 60 additions & 38 deletions src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.intellij.build.events.MessageEvent
import com.intellij.build.progress.BuildProgress
import com.intellij.build.progress.BuildProgressDescriptor
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessListener
import com.intellij.execution.process.ProcessOutput
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
Expand All @@ -26,17 +28,21 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.concurrency.annotations.RequiresReadLock
import org.move.cli.MoveProject.UpdateStatus
import org.move.cli.manifest.MoveToml
import org.move.cli.manifest.TomlDependency.Git.Companion.moveHome
import org.move.cli.runConfigurations.aptos.AptosExitStatus
import org.move.cli.settings.getAptosCli
import org.move.cli.settings.moveSettings
import org.move.lang.toNioPathOrNull
import org.move.lang.toTomlFile
import org.move.openapiext.AnsiEscapedProcessAdapter
import org.move.openapiext.TaskResult
import org.move.openapiext.contentRoots
import org.move.openapiext.runProcessWithGlobalProgress
import org.move.stdext.iterateFiles
import org.move.stdext.unwrapOrElse
import org.move.stdext.withExtended
Expand Down Expand Up @@ -149,19 +155,34 @@ class MoveProjectsSyncTask(
"Fetching dependencies disabled in the plugin settings",
)
} else {
context.runWithChildProgress("Fetching dependencies") { childContext ->
val result = fetchDependencyPackages(childContext, projectRoot)
if (result is TaskResult.Err) {
moveProject =
moveProject.copy(fetchDepsStatus = UpdateStatus.UpdateFailed("Failed to fetch dependency packages"))
context.runWithChildProgress("Fetch dependencies") { childContext ->
val taskResult = fetchDependencyPackages(childContext, projectRoot)
if (taskResult is TaskResult.Err) {
moveProject = moveProject.copy(
fetchDepsStatus = UpdateStatus.UpdateFailed("Failed to fetch dependency packages")
)
}
childContext.checkCanceled()
// fetched any dependencies
if (taskResult is TaskResult.Ok) {
val output = taskResult.value
if (output.stdout.contains("FETCHING GIT DEPENDENCY")) {
childContext.withProgressText("Preparing filesystem")
VfsUtil.markDirtyAndRefresh(
false,
true,
false,
moveHome().toFile()
)
}
}
result
taskResult
}
}
context.checkCanceled()

val deps =
(context.runWithChildProgress("Loading dependencies") { childContext ->
(context.runWithChildProgress("Load modules") { childContext ->
// Blocks till completed or cancelled by the toml / file change
runReadAction {
val rootPackage = moveProject.currentPackage
Expand All @@ -182,11 +203,9 @@ class MoveProjectsSyncTask(
projects.add(moveProject.copy(dependencies = deps))
}

private fun fetchDependencyPackages(childContext: SyncContext, projectRoot: Path): TaskResult<Unit> {
val syncListener =
SyncProcessListener(childContext) { event ->
childContext.syncProgress.output(event.text, true)
}
private fun fetchDependencyPackages(
childContext: SyncContext, projectRoot: Path
): TaskResult<ProcessOutput> {
val skipLatest = project.moveSettings.skipFetchLatestGitDeps
val aptos = project.getAptosCli(parentDisposable = this)
return when {
Expand All @@ -196,15 +215,40 @@ class MoveProjectsSyncTask(
aptos.fetchPackageDependencies(
projectRoot,
skipLatest,
processListener = syncListener
runner = {
// populate progress bar
addProcessListener(object: AnsiEscapedProcessAdapter() {
override fun onColoredTextAvailable(text: String) {
// show progress bars for the long operations
val line = text.trim()
if (line.startsWith("FETCHING GIT DEPENDENCY")) {
val gitRepo = line.substring("FETCHING GIT DEPENDENCY".length)
childContext.withProgressText("Fetching $gitRepo")
}
if (line.startsWith("UPDATING GIT DEPENDENCY")) {
val gitRepo = line.substring("UPDATING GIT DEPENDENCY".length)
childContext.withProgressText("Updating $gitRepo")
}
}
})
addProcessListener(object: ProcessListener {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
childContext.syncProgress.output(event.text, true)
}
})
runProcessWithGlobalProgress()
}
).unwrapOrElse {
return TaskResult.Err(
"Failed to fetch / update dependencies",
it.message
)
}
val output = aptosProcessOutput.output
when (val exitStatus = aptosProcessOutput.exitStatus) {
is AptosExitStatus.Result -> TaskResult.Ok(Unit)
is AptosExitStatus.Result -> {
TaskResult.Ok(output)
}
is AptosExitStatus.Error -> {
if (exitStatus.message.contains("Unable to resolve packages for package")) {
return TaskResult.Err(
Expand All @@ -218,9 +262,9 @@ class MoveProjectsSyncTask(
exitStatus.message.split(": ").joinToString(": \n")
)
}
TaskResult.Ok(Unit)
TaskResult.Ok(output)
}
is AptosExitStatus.Malformed -> TaskResult.Ok(Unit)
is AptosExitStatus.Malformed -> TaskResult.Ok(output)
}
}
}
Expand Down Expand Up @@ -364,28 +408,6 @@ class MoveProjectsSyncTask(
}
}

private class SyncProcessListener(
private val context: MoveProjectsSyncTask.SyncContext,
private val onTextAvailable: (ProcessEvent) -> Unit,
): ProcessProgressListener {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<Any>) {
onTextAvailable(event)
// show progress bars for the long operations
val text = event.text.trim { it <= ' ' }
if (text.startsWith("FETCHING GIT DEPENDENCY")) {
val gitRepo = text.substring("FETCHING GIT DEPENDENCY".length)
context.withProgressText("Fetching $gitRepo")
}
if (text.startsWith("UPDATING GIT DEPENDENCY")) {
val gitRepo = text.substring("UPDATING GIT DEPENDENCY".length)
context.withProgressText("Updating $gitRepo")
}
}

override fun error(title: String, message: String) = context.syncProgress.error(title, message)
override fun warning(title: String, message: String) = context.syncProgress.warning(title, message)
}

private fun <T, R> BuildProgress<BuildProgressDescriptor>.runWithChildProgress(
@BuildEventsNls.Title title: String,
createContext: (BuildProgress<BuildProgressDescriptor>) -> T,
Expand Down
9 changes: 6 additions & 3 deletions src/main/kotlin/org/move/cli/manifest/TomlDependency.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ sealed class TomlDependency {

@RequiresReadLock
override fun rootDirectory(): RsResult<VirtualFile, DependencyError> {
val vFile = VfsUtil.findFile(localPath, true)
val vFile = VfsUtil.findFile(localPath, false)
if (vFile == null) {
return Err(DependencyError("Cannot find dependency folder: $localPath"))
}
Expand All @@ -42,20 +42,23 @@ sealed class TomlDependency {

@RequiresReadLock
override fun rootDirectory(): RsResult<VirtualFile, DependencyError> {
val moveHomePath = SystemProperties.getUserHome().toPath().resolve(".move")
val moveHomePath = moveHome()
if (!moveHomePath.exists()) {
return Err(DependencyError("$moveHomePath directory does not exist"))
}
val depDirName = dependencyDirName(repo, rev)
val depRoot = moveHomePath.resolve(depDirName).resolve(subdir)
val depRootFile = VfsUtil.findFile(depRoot, true)
// NOTE: VFS cannot be refreshed from the read action
val depRootFile = VfsUtil.findFile(depRoot, false)
if (depRootFile == null) {
return Err(DependencyError("cannot find folder: $depRoot"))
}
return Ok(depRootFile)
}

companion object {
fun moveHome(): Path = SystemProperties.getUserHome().toPath().resolve(".move")

fun dependencyDirName(repo: String, rev: String): String {
val sanitizedRepoName = repo.replace(Regex("[/:.@]"), "_")
val aptosRevName = rev.replace("/", "_")
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp
fun fetchPackageDependencies(
projectDir: Path,
skipLatest: Boolean,
processListener: ProcessListener
runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() }
): AptosProcessResult<Unit> {
val commandLine =
AptosCommandLine(
Expand All @@ -73,7 +73,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp
),
workingDirectory = projectDir
)
return executeAptosCommandLine(commandLine, colored = true, listener = processListener)
return executeAptosCommandLine(commandLine, colored = true, runner = runner)
}

fun checkProject(args: AptosCompileArgs): RsResult<ProcessOutput, RsProcessExecutionException.Start> {
Expand Down
31 changes: 31 additions & 0 deletions src/main/kotlin/org/move/openapiext/ProcessOutput.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
package org.move.openapiext

import com.intellij.execution.process.AnsiEscapeDecoder
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.Key

val ProcessOutput.isSuccess: Boolean get() = !isTimeout && !isCancelled && exitCode == 0

/**
* Capturing adapter that removes ANSI escape codes from the output
*/
abstract class AnsiEscapedProcessAdapter: ProcessAdapter(), AnsiEscapeDecoder.ColoredTextAcceptor {
private val decoder = AnsiEscapeDecoder()
private val eolType: Key<Any> = Key.create("end of line")

override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
decoder.escapeText(event.text, outputType, this)
decoder.escapeText("", eolType, this)
}

var buffer = StringBuilder()

override fun coloredTextAvailable(text: String, attributes: Key<*>) {
if (attributes == eolType) {
onColoredTextAvailable(buffer.toString())
buffer = StringBuilder()
} else {
buffer.append(text)
}
}

abstract fun onColoredTextAvailable(text: String)
}