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

parse aptos command line json output #237

Merged
merged 4 commits into from
Nov 10, 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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ allprojects {
}
// cannot be updated further, problems with underlying library
implementation("com.github.ajalt.clikt:clikt:3.5.4")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.+")

testImplementation("junit:junit:4.13.2")
testImplementation("org.opentest4j:opentest4j:1.3.0")
Expand Down
43 changes: 31 additions & 12 deletions src/main/kotlin/org/move/cli/MoveProjectsSyncTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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.runConfigurations.aptos.AptosExitStatus
import org.move.cli.settings.getAptosCli
import org.move.cli.settings.moveSettings
import org.move.lang.toNioPathOrNull
Expand Down Expand Up @@ -182,7 +183,6 @@ class MoveProjectsSyncTask(
}

private fun fetchDependencyPackages(childContext: SyncContext, projectRoot: Path): TaskResult<Unit> {
val taskResult: TaskResult<Unit>? = null
val syncListener =
SyncProcessListener(childContext) { event ->
childContext.syncProgress.output(event.text, true)
Expand All @@ -192,17 +192,36 @@ class MoveProjectsSyncTask(
return when {
aptos == null -> TaskResult.Err("Invalid Aptos CLI configuration")
else -> {
aptos.fetchPackageDependencies(
projectRoot,
skipLatest,
processListener = syncListener
).unwrapOrElse {
return TaskResult.Err(
"Failed to fetch / update dependencies",
it.message
)
val aptosProcessOutput =
aptos.fetchPackageDependencies(
projectRoot,
skipLatest,
processListener = syncListener
).unwrapOrElse {
return TaskResult.Err(
"Failed to fetch / update dependencies",
it.message
)
}
when (val exitStatus = aptosProcessOutput.exitStatus) {
is AptosExitStatus.Result -> TaskResult.Ok(Unit)
is AptosExitStatus.Error -> {
if (exitStatus.message.contains("Unable to resolve packages for package")) {
return TaskResult.Err(
"Unable to resolve packages",
exitStatus.message.split(": ").joinToString(": \n")
)
}
if (!exitStatus.message.contains("Compilation error")) {
return TaskResult.Err(
"Error occurred",
exitStatus.message.split(": ").joinToString(": \n")
)
}
TaskResult.Ok(Unit)
}
is AptosExitStatus.Malformed -> TaskResult.Ok(Unit)
}
TaskResult.Ok(Unit)
}
}
}
Expand Down Expand Up @@ -310,7 +329,7 @@ class MoveProjectsSyncTask(
val depRoot = dep.rootDirectory()
.unwrapOrElse {
syncContext.syncProgress.message(
"Failed to load ${dep.name}.",
"Failed to load ${dep.name}",
"Error when resolving dependency root, \n${it.message}",
MessageEvent.Kind.ERROR,
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ data class AptosCommandLine(
.withParameters(this.arguments)
.withWorkingDirectory(this.workingDirectory)
.withCharset(Charsets.UTF_8)
// disables default coloring for stderr
.withRedirectErrorStream(true)
this.environmentVariables.configureCommandLine(generalCommandLine, true)
return generalCommandLine
}
Expand Down
79 changes: 38 additions & 41 deletions src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.move.cli.runConfigurations.aptos

import com.fasterxml.jackson.core.JacksonException
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessListener
Expand All @@ -9,12 +10,9 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.execution.ParametersListUtil
import org.move.cli.MvConstants
import org.move.cli.MoveProject
import org.move.cli.externalLinter.ExternalLinter
import org.move.cli.externalLinter.externalLinterSettings
import org.move.cli.MvConstants
import org.move.cli.runConfigurations.AptosCommandLine
import org.move.cli.settings.moveSettings
import org.move.openapiext.*
import org.move.openapiext.common.isUnitTestMode
import org.move.stdext.RsResult
Expand Down Expand Up @@ -56,17 +54,17 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp
executeCommandLine(commandLine).unwrapOrElse { return Err(it) }

fullyRefreshDirectory(rootDirectory)

val manifest =
checkNotNull(rootDirectory.findChild(MvConstants.MANIFEST_FILE)) { "Can't find the manifest file" }

return Ok(manifest)
}

fun fetchPackageDependencies(
projectDir: Path,
skipLatest: Boolean,
processListener: ProcessListener
): RsProcessResult<Unit> {
): AptosProcessResult<Unit> {
val commandLine =
AptosCommandLine(
subCommand = "move compile",
Expand All @@ -75,10 +73,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp
),
workingDirectory = projectDir
)
commandLine
.toColoredCommandLine(this.cliLocation)
.execute(innerDisposable, listener = processListener)
return Ok(Unit)
return executeAptosCommandLine(commandLine, colored = true, listener = processListener)
}

fun checkProject(args: AptosCompileArgs): RsResult<ProcessOutput, RsProcessExecutionException.Start> {
Expand Down Expand Up @@ -179,44 +174,46 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp

private fun executeCommandLine(
commandLine: AptosCommandLine,
colored: Boolean = false,
listener: ProcessListener? = null,
runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() }
): RsProcessResult<ProcessOutput> {
return commandLine
.toGeneralCommandLine(this.cliLocation)
.execute(innerDisposable, stdIn = null, listener = listener, runner = runner)
val generalCommandLine = if (colored) {
commandLine.toColoredCommandLine(this.cliLocation)
} else {
commandLine.toGeneralCommandLine(this.cliLocation)
}
return generalCommandLine.execute(innerDisposable, runner, listener)
}

override fun dispose() {}
}

data class AptosCompileArgs(
val linter: ExternalLinter,
val moveProjectDirectory: Path,
val extraArguments: String,
val envs: Map<String, String>,
val enableMove2: Boolean,
val skipLatestGitDeps: Boolean,
) {
companion object {
fun forMoveProject(moveProject: MoveProject): AptosCompileArgs {
val linterSettings = moveProject.project.externalLinterSettings
val moveSettings = moveProject.project.moveSettings

val additionalArguments = linterSettings.additionalArguments
val enviroment = linterSettings.envs
val workingDirectory = moveProject.workingDirectory

return AptosCompileArgs(
linterSettings.tool,
workingDirectory,
additionalArguments,
enviroment,
enableMove2 = moveSettings.enableMove2,
skipLatestGitDeps = moveSettings.skipFetchLatestGitDeps
)
private fun executeAptosCommandLine(
commandLine: AptosCommandLine,
colored: Boolean = false,
listener: ProcessListener? = null,
runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() }
): AptosProcessResult<Unit> {
val processOutput = executeCommandLine(commandLine, colored, listener, runner)
.unwrapOrElse {
if (it !is RsProcessExecutionException.FailedWithNonZeroExitCode) {
return Err(it)
}
it.output
}

val json = processOutput.stdout
.lines().dropWhile { l -> !l.startsWith("{") }.joinToString("\n").trim()
val exitStatus = try {
AptosExitStatus.fromJson(json)
} catch (e: JacksonException) {
return Err(RsDeserializationException(e))
}
val aptosProcessOutput = AptosProcessOutput(Unit, processOutput, exitStatus)

return Ok(aptosProcessOutput)
}

override fun dispose() {}
}


val MoveProject.workingDirectory: Path get() = this.currentPackage.contentRoot.pathAsPath
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.move.cli.runConfigurations.aptos

import org.move.cli.MoveProject
import org.move.cli.externalLinter.ExternalLinter
import org.move.cli.externalLinter.externalLinterSettings
import org.move.cli.settings.moveSettings
import java.nio.file.Path

data class AptosCompileArgs(
val linter: ExternalLinter,
val moveProjectDirectory: Path,
val extraArguments: String,
val envs: Map<String, String>,
val enableMove2: Boolean,
val skipLatestGitDeps: Boolean,
) {
companion object {
fun forMoveProject(moveProject: MoveProject): AptosCompileArgs {
val linterSettings = moveProject.project.externalLinterSettings
val moveSettings = moveProject.project.moveSettings

val additionalArguments = linterSettings.additionalArguments
val enviroment = linterSettings.envs
val workingDirectory = moveProject.workingDirectory

return AptosCompileArgs(
linterSettings.tool,
workingDirectory,
additionalArguments,
enviroment,
enableMove2 = moveSettings.enableMove2,
skipLatestGitDeps = moveSettings.skipFetchLatestGitDeps
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.move.cli.runConfigurations.aptos

import com.fasterxml.jackson.core.JacksonException
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import org.intellij.lang.annotations.Language

sealed class AptosExitStatus(val message: String) {
class Result(message: String): AptosExitStatus(message)
class Error(message: String): AptosExitStatus(message)
class Malformed(jsonText: String): AptosExitStatus(jsonText)

companion object {
@Throws(JacksonException::class)
fun fromJson(@Language("JSON") json: String): AptosExitStatus {
val parsedResult = JSON_MAPPER.readValue(json, AptosJsonResult::class.java)
return when {
parsedResult.Error != null -> Error(parsedResult.Error)
parsedResult.Result != null -> Result(parsedResult.Result)
else -> Malformed(json)
}
}
}
}

@Suppress("PropertyName")
private data class AptosJsonResult(
val Result: String?,
val Error: String?
)

private val JSON_MAPPER: ObjectMapper = ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerKotlinModule()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.move.cli.runConfigurations.aptos

import com.intellij.execution.process.ProcessOutput
import org.move.openapiext.RsProcessExecutionOrDeserializationException
import org.move.stdext.RsResult

typealias AptosProcessResult<T> = RsResult<AptosProcessOutput<T>, RsProcessExecutionOrDeserializationException>

data class AptosProcessOutput<T>(
val item: T,
val output: ProcessOutput,
val exitStatus: AptosExitStatus,
) {
fun <T, U> replaceItem(newItem: U): AptosProcessOutput<U> = AptosProcessOutput(newItem, output, exitStatus)
}
6 changes: 1 addition & 5 deletions src/main/kotlin/org/move/openapiext/CommandLineExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ fun GeneralCommandLine.execute(): ProcessOutput? {
/// `owner` parameter represents the object whose lifetime it's using for the process lifetime
fun GeneralCommandLine.execute(
owner: CheckedDisposable,
stdIn: ByteArray? = null,
runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() },
listener: ProcessListener? = null
): RsProcessResult<ProcessOutput> {
Expand Down Expand Up @@ -84,9 +83,6 @@ fun GeneralCommandLine.execute(
listener?.let { processHandler.addProcessListener(it) }

val output = try {
if (stdIn != null) {
processHandler.processInput.use { it.write(stdIn) }
}
// execution happens here
processHandler.runner()
} finally {
Expand All @@ -101,7 +97,7 @@ fun GeneralCommandLine.execute(
output.isCancelled -> RsResult.Err(RsProcessExecutionException.Canceled(commandLineString, output))
output.isTimeout -> RsResult.Err(RsProcessExecutionException.Timeout(commandLineString, output))
output.exitCode != 0 -> RsResult.Err(
RsProcessExecutionException.ProcessAborted(
RsProcessExecutionException.FailedWithNonZeroExitCode(
commandLineString,
output
)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/openapiext/MvProcessResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ sealed class RsProcessExecutionException : RsProcessExecutionOrDeserializationEx
) : RsProcessExecutionException(errorMessage(commandLineString, output))

/** The process exited with non-zero exit code */
class ProcessAborted(
class FailedWithNonZeroExitCode(
override val commandLineString: String,
val output: ProcessOutput,
) : RsProcessExecutionException(errorMessage(commandLineString, output))
Expand All @@ -65,6 +65,6 @@ fun RsProcessResult<ProcessOutput>.ignoreExitCode(): RsResult<ProcessOutput, RsP
is RsProcessExecutionException.Start -> RsResult.Err(err)
is RsProcessExecutionException.Canceled -> RsResult.Ok(err.output)
is RsProcessExecutionException.Timeout -> RsResult.Ok(err.output)
is RsProcessExecutionException.ProcessAborted -> RsResult.Ok(err.output)
is RsProcessExecutionException.FailedWithNonZeroExitCode -> RsResult.Ok(err.output)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.move.cli.runConfigurations.aptos

import org.move.utils.tests.MvTestBase

class AptosExitStatusTest: MvTestBase() {
fun `test parse result`() {
val status = AptosExitStatus.fromJson(
"""
{
"Result": "my result message"
}
"""
)
check(status is AptosExitStatus.Result)
check(status.message == "my result message")
}

fun `test parse error`() {
val status = AptosExitStatus.fromJson(
"""
{
"Error": "my error message"
}
"""
)
check(status is AptosExitStatus.Error)
check(status.message == "my error message")
}

fun `test parse malformed`() {
val status = AptosExitStatus.fromJson(
"""
{
"Unknown": "unknown"
}
"""
)
check(status is AptosExitStatus.Malformed)
}
}