MavenCommandExecutor.kt
package com.depanalyzer.parser.maven
import com.depanalyzer.cli.ProgressTracker
import java.io.File
import java.io.InputStream
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
object MavenCommandExecutor {
private const val DEFAULT_TIMEOUT_SECONDS = 1800L
fun execute(
projectDir: File,
timeout: Duration = DEFAULT_TIMEOUT_SECONDS.seconds,
verbose: Boolean = false,
isDefaultTimeout: Boolean = true,
onOutputLine: ((String) -> Unit)? = null
): String? = try {
if (!projectDir.exists() || !projectDir.isDirectory) {
if (verbose) System.err.println("[MavenCommandExecutor] Project directory doesn't exist or is not a directory")
return null
}
val pomFile = File(projectDir, "pom.xml")
if (!pomFile.exists()) {
if (verbose) System.err.println("[MavenCommandExecutor] pom.xml not found in $projectDir")
return null
}
val mavenCommand = MavenDetector.findMavenCommand(projectDir, verbose)
?: run {
if (verbose) System.err.println("[MavenCommandExecutor] Maven not found (no wrapper and no global mvn)")
return null
}
ProgressTracker.logProcessing("Descargando dependencias (puede tardar varios minutos)...")
if (isDefaultTimeout) {
ProgressTracker.logStep(" ⏳ Se cancelará en: ${timeout.inWholeSeconds}s (30 minutos)")
} else {
ProgressTracker.logStep(" ⏳ Timeout: ${timeout.inWholeSeconds}s")
}
if (verbose) System.err.println("[MavenCommandExecutor] Executing 'mvn dependency:tree' in $projectDir using: $mavenCommand")
val process = ProcessBuilder(mavenCommand, "dependency:tree")
.directory(projectDir)
.redirectErrorStream(true)
.start()
val outputBuffer = StringBuilder()
val outputReader = consumeStream(
inputStream = process.inputStream,
sink = outputBuffer,
onOutputLine = onOutputLine
)
val completed = process.waitFor(timeout.inWholeSeconds, TimeUnit.SECONDS)
if (!completed) {
process.destroyForcibly()
outputReader.join(1000)
if (verbose) System.err.println("[MavenCommandExecutor] Execution timeout after ${timeout.inWholeSeconds} seconds")
return null
}
outputReader.join(3000)
val output = outputBuffer.toString()
val exitCode = process.exitValue()
if (verbose) {
System.err.println("[MavenCommandExecutor] Execution completed with exit code $exitCode")
System.err.println("[MavenCommandExecutor] Output length: ${output.length} characters")
}
if (output.isBlank()) {
if (verbose) System.err.println("[MavenCommandExecutor] No output received from dependency:tree")
if (exitCode != 0) {
if (verbose) System.err.println("[MavenCommandExecutor] Exit code was $exitCode and output is empty")
}
return null
}
if (exitCode != 0 && verbose) {
System.err.println("[MavenCommandExecutor] Non-zero exit code ($exitCode), but returning output anyway (likely warnings)")
}
ProgressTracker.logSuccess("Árbol de dependencias resuelto (${output.length} chars)")
output
} catch (e: Exception) {
if (verbose) {
System.err.println("[MavenCommandExecutor] Exception during command execution: ${e.message}")
e.printStackTrace(System.err)
}
null
}
private fun consumeStream(
inputStream: InputStream,
sink: StringBuilder,
onOutputLine: ((String) -> Unit)?
): Thread {
return Thread {
inputStream.bufferedReader().useLines { lines ->
lines.forEach { line ->
synchronized(sink) {
sink.appendLine(line)
}
onOutputLine?.invoke(line)
}
}
}.apply {
isDaemon = true
start()
}
}
}