internal/platform/utils/utils.go (251 lines of code) (raw):

/* * Copyright 2021-2024 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package utils import ( "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "github.com/JetBrains/qodana-cli/internal/platform/msg" "github.com/JetBrains/qodana-cli/internal/platform/qdenv" "github.com/JetBrains/qodana-cli/internal/platform/strutil" "github.com/pterm/pterm" "github.com/shirou/gopsutil/v3/process" log "github.com/sirupsen/logrus" ) // CheckDirFiles checks if a directory contains files. func CheckDirFiles(dir string) bool { files, err := os.ReadDir(dir) if err != nil { return false } return len(files) > 0 } // FindFiles returns a slice of files with the given extensions from the given root (recursive). func FindFiles(root string, extensions []string) []string { var files []string err := filepath.Walk( root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } fileExtension := filepath.Ext(path) if strutil.Contains(extensions, fileExtension) { files = append(files, path) } return nil }, ) if err != nil { log.Fatal(err) } return files } func GetJavaExecutablePath() (string, error) { var java string var err error var ret int //goland:noinspection GoBoolExpressions if runtime.GOOS == "windows" { java, _, ret, err = RunCmdRedirectOutput("", "java -XshowSettings:properties -version 2>&1 | findstr java.home") } else { java, _, ret, err = RunCmdRedirectOutput("", "java -XshowSettings:properties -version 2>&1 | grep java.home") } if err != nil || ret != 0 { return "", fmt.Errorf( "failed to get JAVA_HOME: %w, %d. Check that java executable is accessible from the PATH", err, ret, ) } split := strings.Split(java, "=") if len(split) < 2 { return "", fmt.Errorf( "failed to get JAVA_HOME: %s. Check that java executable is accessible from the PATH", java, ) } javaHome := split[1] javaHome = strings.Trim(javaHome, "\r\n ") var javaExecFileName string //goland:noinspection GoBoolExpressions if runtime.GOOS == "windows" { javaExecFileName = "java.exe" } else { javaExecFileName = "java" } javaExecutablePath := filepath.Join(javaHome, "bin", javaExecFileName) return javaExecutablePath, nil } // LaunchAndLog launches a process and logs its output. func LaunchAndLog(logDir string, executable string, args ...string) (string, string, int, error) { stdout, stderr, ret, err := RunCmdRedirectOutput("", args...) if err != nil { log.Error(fmt.Errorf("failed to run %s: %w", executable, err)) return "", "", ret, err } fmt.Println(stdout) if stderr != "" { _, _ = fmt.Fprintln(os.Stderr, stderr) } if err := AppendToFile(filepath.Join(logDir, executable+"-out.log"), stdout); err != nil { log.Error(err) } if err := AppendToFile(filepath.Join(logDir, executable+"-err.log"), stderr); err != nil { log.Error(err) } return stdout, stderr, ret, nil } // DownloadFile downloads a file from a given URL to a given filepath. func DownloadFile(filepath string, url string, auth string, spinner *pterm.SpinnerPrinter) error { headReq, err := http.NewRequest("HEAD", url, nil) if err != nil { return fmt.Errorf("error creating HEAD request: %w", err) } if auth != "" { headReq.Header.Add("Authorization", fmt.Sprintf("Bearer %s", auth)) } response, err := http.DefaultClient.Do(headReq) if err != nil { return fmt.Errorf("error making HEAD request: %w", err) } if response.StatusCode != http.StatusOK { return fmt.Errorf("response from %s (HEAD): %s", url, response.Status) } sizeStr := response.Header.Get("Content-Length") if sizeStr == "" { sizeStr = "-1" } size, err := strconv.Atoi(sizeStr) if err != nil { return fmt.Errorf("error converting Content-Length to integer: %w", err) } getReq, err := http.NewRequest("GET", url, nil) if err != nil { return fmt.Errorf("error creating GET request: %w", err) } if auth != "" { getReq.Header.Add("Authorization", fmt.Sprintf("Bearer %s", auth)) } resp, err := http.DefaultClient.Do(getReq) if err != nil { return fmt.Errorf("error making GET request: %w", err) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("response from %s (GET): %s", url, resp.Status) } defer func(Body io.ReadCloser) { if err := Body.Close(); err != nil { fmt.Printf("Error while closing HTTP stream: %v\n", err) } }(resp.Body) out, err := os.Create(filepath) if err != nil { return fmt.Errorf("error creating file: %w", err) } defer func(out *os.File) { if err := out.Close(); err != nil { fmt.Printf("Error while closing output file: %v\n", err) } }(out) buffer := make([]byte, 1024) total := 0 lastTotal := 0 text := "" if spinner != nil { text = spinner.Text } for { length, err := resp.Body.Read(buffer) if err != nil && err != io.EOF { return fmt.Errorf("error reading response body: %w", err) } total += length if spinner != nil && total-lastTotal > 1024*1024 { lastTotal = total spinner.UpdateText(fmt.Sprintf("%s (%d %%)", text, 100*total/size)) } if length == 0 { break } if _, err = out.Write(buffer[:length]); err != nil { return fmt.Errorf("error writing to file: %w", err) } } // Check if the size matches, but only if the Content-Length header was present and valid if size > 0 && total != size { return fmt.Errorf("downloaded file size doesn't match expected size, got %d, expected %d", total, size) } if spinner != nil { spinner.UpdateText(fmt.Sprintf("%s (100 %%)", text)) } return nil } func GetDefaultUser() string { switch runtime.GOOS { case "windows": return "root" default: // "darwin", "linux", "freebsd", "openbsd", "netbsd" return fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()) } } // FindProcess using gopsutil to find process by name. func FindProcess(processName string) bool { if qdenv.IsContainer() { return IsProcess(processName) } p, err := process.Processes() if err != nil { log.Fatal(err) } for _, proc := range p { name, err := proc.Name() if err == nil { if name == processName { return true } } } return false } // IsProcess returns true if a process with cmd containing 'find' substring exists. func IsProcess(find string) bool { processes, err := process.Processes() if err != nil { return false } for _, proc := range processes { cmd, err := proc.Cmdline() if err != nil { continue } if strings.Contains(cmd, find) { return true } } return false } // IsInstalled checks if git is installed. func IsInstalled(what string) bool { help := "" if what == "git" { help = ", refer to https://git-scm.com/downloads for installing it" } _, err := exec.LookPath(what) if err != nil { msg.WarningMessage( "Unable to find %s"+help, what, ) return false } return true } func OpenBrowser(url string) error { var cmd string var args []string switch runtime.GOOS { case "windows": cmd = "cmd" args = []string{"/c", "start"} case "darwin": cmd = "open" default: // "linux", "freebsd", "openbsd", "netbsd" cmd = "xdg-open" } args = append(args, url) return exec.Command(cmd, args...).Start() }