diff --git a/ls/ls.go b/ls/ls.go index 17e4f56..f5d734a 100644 --- a/ls/ls.go +++ b/ls/ls.go @@ -22,6 +22,7 @@ import ( "io" "log" "os" + "os/exec" "strconv" "strings" "sync" @@ -50,8 +51,10 @@ type INOLanguageServer struct { progressHandler *progressProxyHandler closing chan bool + removeTempMutex sync.Mutex clangdStarted *sync.Cond dataMux sync.RWMutex + tempDir *paths.Path buildPath *paths.Path buildSketchRoot *paths.Path buildSketchCpp *paths.Path @@ -144,18 +147,21 @@ func NewINOLanguageServer(stdin io.Reader, stdout io.Writer, config *Config) *IN if tmp, err := paths.MkTempDir("", "arduino-language-server"); err != nil { log.Fatalf("Could not create temp folder: %s", err) } else { - ls.buildPath = tmp.Canonical() - ls.buildSketchRoot = ls.buildPath.Join("sketch") + ls.tempDir = tmp.Canonical() } - - if tmp, err := paths.MkTempDir("", "arduino-language-server"); err != nil { + ls.buildPath = ls.tempDir.Join("build") + ls.buildSketchRoot = ls.buildPath.Join("sketch") + if err := ls.buildPath.MkdirAll(); err != nil { + log.Fatalf("Could not create temp folder: %s", err) + } + ls.fullBuildPath = ls.tempDir.Join("fullbuild") + if err := ls.fullBuildPath.MkdirAll(); err != nil { log.Fatalf("Could not create temp folder: %s", err) - } else { - ls.fullBuildPath = tmp.Canonical() } logger.Logf("Initial board configuration: %s", ls.config.Fqbn) logger.Logf("%s", globals.VersionInfo.String()) + logger.Logf("Language server temp directory: %s", ls.tempDir) logger.Logf("Language server build path: %s", ls.buildPath) logger.Logf("Language server build sketch root: %s", ls.buildSketchRoot) logger.Logf("Language server FULL build path: %s", ls.fullBuildPath) @@ -387,6 +393,7 @@ func (ls *INOLanguageServer) shutdownReqFromIDE(ctx context.Context, logger json close(done) }() _, _ = ls.Clangd.conn.Shutdown(context.Background()) + ls.removeTemporaryFiles(logger) <-done return nil } @@ -1371,6 +1378,38 @@ func (ls *INOLanguageServer) setTraceNotifFromIDE(logger jsonrpc.FunctionLogger, ls.Clangd.conn.SetTrace(params) } +func (ls *INOLanguageServer) removeTemporaryFiles(logger jsonrpc.FunctionLogger) { + ls.removeTempMutex.Lock() + defer ls.removeTempMutex.Unlock() + + if ls.tempDir == nil { + // Nothing to remove + return + } + + // Start a detached process to remove the temp files + cwd, err := os.Getwd() + if err != nil { + logger.Logf("Error getting current working directory: %s", err) + return + } + cmd := exec.Command(os.Args[0], "remove-temp-files", ls.tempDir.String()) + cmd.Dir = cwd + if err := cmd.Start(); err != nil { + logger.Logf("Error starting remove-temp-files process: %s", err) + return + } + + // The process is now started, we can reset the paths + ls.buildPath, ls.fullBuildPath, ls.buildSketchRoot, ls.tempDir = nil, nil, nil, nil + + // Detach the process so it can continue running even if the parent process exits + if err := cmd.Process.Release(); err != nil { + logger.Logf("Error detaching remove-temp-files process: %s", err) + return + } +} + // Close closes all the json-rpc connections and clean-up temp folders. func (ls *INOLanguageServer) Close() { if ls.Clangd != nil { @@ -1381,14 +1420,6 @@ func (ls *INOLanguageServer) Close() { close(ls.closing) ls.closing = nil } - if ls.buildPath != nil { - ls.buildPath.RemoveAll() - ls.buildPath = nil - } - if ls.fullBuildPath != nil { - ls.fullBuildPath.RemoveAll() - ls.fullBuildPath = nil - } } // CloseNotify returns a channel that is closed when the InoHandler is closed diff --git a/ls/lsp_client_clangd.go b/ls/lsp_client_clangd.go index 83789bb..d4b9322 100644 --- a/ls/lsp_client_clangd.go +++ b/ls/lsp_client_clangd.go @@ -60,7 +60,12 @@ func newClangdLSPClient(logger jsonrpc.FunctionLogger, dataFolder *paths.Path, l logger.Logf(" Starting clangd: %s", strings.Join(args, " ")) var clangdStdin io.WriteCloser var clangdStdout, clangdStderr io.ReadCloser - if clangdCmd, err := executils.NewProcess(nil, args...); err != nil { + var extraEnv []string + if ls.tempDir != nil { + extraEnv = append(extraEnv, "TMPDIR="+ls.tempDir.String()) // For unix-based systems + extraEnv = append(extraEnv, "TMP="+ls.tempDir.String()) // For Windows + } + if clangdCmd, err := executils.NewProcess(extraEnv, args...); err != nil { panic("starting clangd: " + err.Error()) } else if cin, err := clangdCmd.StdinPipe(); err != nil { panic("getting clangd stdin: " + err.Error()) diff --git a/ls/lsp_server_ide.go b/ls/lsp_server_ide.go index 5fbe3b5..28228cf 100644 --- a/ls/lsp_server_ide.go +++ b/ls/lsp_server_ide.go @@ -303,8 +303,6 @@ func (server *IDELSPServer) WorkspaceDidChangeConfiguration(logger jsonrpc.Funct // // Since ALS doesn’t have any workspace configuration yet, // ignore it. - return - } // WorkspaceDidChangeWatchedFiles is not implemented diff --git a/main.go b/main.go index 3333672..ed38ad5 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "os/signal" "os/user" "path" + "strings" "github.com/arduino/arduino-language-server/ls" "github.com/arduino/arduino-language-server/streams" @@ -20,6 +21,19 @@ import ( ) func main() { + if len(os.Args) > 1 && os.Args[1] == "remove-temp-files" { + for _, tmpFile := range os.Args[2:] { + // SAFETY CHECK + if !strings.Contains(tmpFile, "arduino-language-server") { + fmt.Println("Could not remove extraneous temp folder:", tmpFile) + os.Exit(1) + } + + paths.New(tmpFile).RemoveAll() + } + return + } + clangdPath := flag.String( "clangd", "", "Path to clangd executable")