78 lines
2.0 KiB
Go
78 lines
2.0 KiB
Go
|
|
package platform
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"context"
|
|||
|
|
"errors"
|
|||
|
|
"os/exec"
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
"unicode/utf16"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// CommandError 保存后台命令失败时的原始诊断信息。
|
|||
|
|
// 服务层会把这些输出映射成稳定错误码,界面再按当前语言展示友好提示。
|
|||
|
|
type CommandError struct {
|
|||
|
|
Command string
|
|||
|
|
Args []string
|
|||
|
|
ExitCode int
|
|||
|
|
Output string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (e *CommandError) Error() string {
|
|||
|
|
if strings.TrimSpace(e.Output) != "" {
|
|||
|
|
return strings.TrimSpace(e.Output)
|
|||
|
|
}
|
|||
|
|
if e.ExitCode != 0 {
|
|||
|
|
return e.Command + " exited with code " + strconv.Itoa(e.ExitCode)
|
|||
|
|
}
|
|||
|
|
return e.Command + " failed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RunHidden 在后台执行命令并合并 stdout/stderr。
|
|||
|
|
// Windows 下由平台文件设置 HideWindow 和 CREATE_NO_WINDOW,避免 Git/WSL 查询弹出终端窗口。
|
|||
|
|
func RunHidden(ctx context.Context, name string, args ...string) (string, error) {
|
|||
|
|
cmd := exec.CommandContext(ctx, name, args...)
|
|||
|
|
configureHidden(cmd)
|
|||
|
|
var stdout, stderr bytes.Buffer
|
|||
|
|
cmd.Stdout = &stdout
|
|||
|
|
cmd.Stderr = &stderr
|
|||
|
|
e := cmd.Run()
|
|||
|
|
out := strings.TrimSpace(strings.ReplaceAll(decodeOutput(stdout.Bytes()), "\x00", ""))
|
|||
|
|
errOut := strings.TrimSpace(strings.ReplaceAll(decodeOutput(stderr.Bytes()), "\x00", ""))
|
|||
|
|
if e != nil {
|
|||
|
|
if out == "" {
|
|||
|
|
out = errOut
|
|||
|
|
} else if errOut != "" {
|
|||
|
|
out += "\n" + errOut
|
|||
|
|
}
|
|||
|
|
if out == "" {
|
|||
|
|
out = e.Error()
|
|||
|
|
}
|
|||
|
|
exitCode := -1
|
|||
|
|
var exit *exec.ExitError
|
|||
|
|
if errors.As(e, &exit) {
|
|||
|
|
exitCode = exit.ExitCode()
|
|||
|
|
}
|
|||
|
|
return "", &CommandError{Command: name, Args: args, ExitCode: exitCode, Output: out}
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodeWSLList 兼容部分 Windows 版本返回的 UTF-16LE 发行版列表。
|
|||
|
|
func DecodeWSLList(b []byte) string { return decodeOutput(b) }
|
|||
|
|
|
|||
|
|
func decodeOutput(b []byte) string {
|
|||
|
|
if len(b) >= 2 && (b[1] == 0 || b[0] == 0xff && b[1] == 0xfe) {
|
|||
|
|
if len(b)%2 != 0 {
|
|||
|
|
b = b[:len(b)-1]
|
|||
|
|
}
|
|||
|
|
u := make([]uint16, 0, len(b)/2)
|
|||
|
|
for i := 0; i+1 < len(b); i += 2 {
|
|||
|
|
u = append(u, uint16(b[i])|uint16(b[i+1])<<8)
|
|||
|
|
}
|
|||
|
|
return strings.TrimPrefix(string(utf16.Decode(u)), "\ufeff")
|
|||
|
|
}
|
|||
|
|
return string(b)
|
|||
|
|
}
|