1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package bench.flame.diff.interop
17 
18 import bench.flame.diff.config.Paths
19 import com.github.ajalt.clikt.core.CliktCommand
20 import com.zaxxer.nuprocess.NuAbstractProcessHandler
21 import com.zaxxer.nuprocess.NuProcessBuilder
22 import java.nio.ByteBuffer
23 import java.nio.file.Path
24 import java.util.concurrent.TimeUnit
25 
26 internal class Shell(private val cwd: Path) {
execnull27     fun exec(vararg command: String): ExecutionResult {
28         val handler = object : NuAbstractProcessHandler() {
29             var outs = StringBuilder()
30             var errs = StringBuilder()
31             var exitCode: Int = 0
32 
33             override fun onExit(exitCode: Int) {
34                 this.exitCode = exitCode
35             }
36 
37             override fun onStdout(buffer: ByteBuffer, closed: Boolean) = consumeLines(buffer, outs)
38 
39             override fun onStderr(buffer: ByteBuffer, closed: Boolean) = consumeLines(buffer, errs)
40 
41             private fun consumeLines(srcBuffer: ByteBuffer, destination: StringBuilder) {
42                 val dstBuffer = ByteArray(srcBuffer.remaining())
43                 srcBuffer.get(dstBuffer)
44                 destination.append(String(dstBuffer))
45             }
46         }
47 
48         NuProcessBuilder(command.asList()).also {
49             it.setCwd(cwd)
50             it.setProcessListener(handler)
51         }.start().waitFor(0, TimeUnit.SECONDS)
52 
53         return ExecutionResult(handler.exitCode, handler.outs.toString(), handler.errs.toString())
54     }
55 
56     data class ExecutionResult(val exitCode: Int, val stdOut: String, val stdErr: String)
57 }
58 
59 internal val Shell.ExecutionResult.output get() = stdOut + stdErr
60 
execWithChecksnull61 internal fun CliktCommand.execWithChecks(
62     vararg command: String,
63     cwd: Path = Paths.currentDir,
64     checkIsSuccess: (Shell.ExecutionResult) -> Boolean = { it.exitCode == 0 },
<lambda>null65     onError: (Shell.ExecutionResult) -> Unit = {
66         exitProcessWithError(
67             "error occurred while executing command '${command.asList()}'." +
68                     "  \nexit code: ${it.exitCode}" +
69                     "  \nstdout: ${it.stdOut}" +
70                     "  \nstderr: ${it.stdErr}"
71         )
72     },
<lambda>null73     onSuccess: (Shell.ExecutionResult) -> Unit = { },
74 ) {
75     val result = Shell(cwd).exec(*command)
76     if (!checkIsSuccess(result)) onError(result) else onSuccess(result)
77 }
78