Skip to content

Changed when the build runs with or without the lib discovery #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions ls/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,24 @@ import (
"google.golang.org/grpc"
)

type rebuildSketchParams struct {
trigger chan chan<- bool
fullRebuild bool
}

type SketchRebuilder struct {
ls *INOLanguageServer
trigger chan chan<- bool
cancel func()
mutex sync.Mutex
ls *INOLanguageServer
params *rebuildSketchParams
cancel func()
mutex sync.Mutex
}

func NewSketchBuilder(ls *INOLanguageServer) *SketchRebuilder {
p := rebuildSketchParams{trigger: make(chan chan<- bool, 1), fullRebuild: false}
res := &SketchRebuilder{
trigger: make(chan chan<- bool, 1),
cancel: func() {},
ls: ls,
params: &p,
cancel: func() {},
ls: ls,
}
go func() {
defer streams.CatchAndLogPanic()
Expand All @@ -45,38 +51,39 @@ func NewSketchBuilder(ls *INOLanguageServer) *SketchRebuilder {
return res
}

func (ls *INOLanguageServer) triggerRebuildAndWait(logger jsonrpc.FunctionLogger) {
func (ls *INOLanguageServer) triggerRebuildAndWait(logger jsonrpc.FunctionLogger, fullRebuild bool) {
completed := make(chan bool)
ls.sketchRebuilder.TriggerRebuild(completed)
ls.sketchRebuilder.TriggerRebuild(completed, fullRebuild)
ls.writeUnlock(logger)
<-completed
ls.writeLock(logger, true)
}

func (ls *INOLanguageServer) triggerRebuild() {
ls.sketchRebuilder.TriggerRebuild(nil)
func (ls *INOLanguageServer) triggerRebuild(fullRebuild bool) {
ls.sketchRebuilder.TriggerRebuild(nil, fullRebuild)
}

func (r *SketchRebuilder) TriggerRebuild(completed chan<- bool) {
func (r *SketchRebuilder) TriggerRebuild(completed chan<- bool, fullRebuild bool) {
r.mutex.Lock()
defer r.mutex.Unlock()

r.cancel() // Stop possibly already running builds
r.params.fullRebuild = fullRebuild
select {
case r.trigger <- completed:
case r.params.trigger <- completed:
default:
}
}

func (r *SketchRebuilder) rebuilderLoop() {
logger := NewLSPFunctionLogger(color.HiMagentaString, "SKETCH REBUILD: ")
for {
completed := <-r.trigger
completed := <-r.params.trigger

for {
// Concede a 200ms delay to accumulate bursts of changes
select {
case <-r.trigger:
case <-r.params.trigger:
continue
case <-time.After(time.Second):
}
Expand All @@ -88,11 +95,12 @@ func (r *SketchRebuilder) rebuilderLoop() {

ctx, cancel := context.WithCancel(context.Background())
r.mutex.Lock()
logger.Logf("Sketch rebuild started")
fullRebuild := r.params.fullRebuild
logger.Logf("Sketch rebuild started. Full-rebuild: %v", fullRebuild)
r.cancel = cancel
r.mutex.Unlock()

if err := r.doRebuild(ctx, logger); err != nil {
if err := r.doRebuild(ctx, fullRebuild, logger); err != nil {
logger.Logf("Error: %s", err)
}

Expand All @@ -104,10 +112,16 @@ func (r *SketchRebuilder) rebuilderLoop() {
}
}

func (r *SketchRebuilder) doRebuild(ctx context.Context, logger jsonrpc.FunctionLogger) error {
func (r *SketchRebuilder) doRebuild(ctx context.Context, fullRebuild bool, logger jsonrpc.FunctionLogger) error {
ls := r.ls
var buildPath *paths.Path
if fullRebuild {
buildPath = ls.fullBuildPath
} else {
buildPath = ls.buildPath
}

if success, err := ls.generateBuildEnvironment(ctx, logger); err != nil {
if success, err := ls.generateBuildEnvironment(ctx, fullRebuild, buildPath, logger); err != nil {
return err
} else if !success {
return fmt.Errorf("build failed")
Expand All @@ -123,14 +137,12 @@ func (r *SketchRebuilder) doRebuild(ctx context.Context, logger jsonrpc.Function
default:
}

if err := ls.buildPath.Join("compile_commands.json").CopyTo(ls.compileCommandsDir.Join("compile_commands.json")); err != nil {
logger.Logf("ERROR: updating compile_commands: %s", err)
}
ls.CopyBuildResults(logger, buildPath, fullRebuild)

if cppContent, err := ls.buildSketchCpp.ReadFile(); err == nil {
oldVesrion := ls.sketchMapper.CppText.Version
oldVersion := ls.sketchMapper.CppText.Version
ls.sketchMapper = sourcemapper.CreateInoMapper(cppContent)
ls.sketchMapper.CppText.Version = oldVesrion + 1
ls.sketchMapper.CppText.Version = oldVersion + 1
ls.sketchMapper.DebugLogAll()
} else {
return errors.WithMessage(err, "reading generated cpp file from sketch")
Expand All @@ -143,7 +155,7 @@ func (r *SketchRebuilder) doRebuild(ctx context.Context, logger jsonrpc.Function
TextDocument: lsp.TextDocumentIdentifier{URI: cppURI},
}
if err := ls.Clangd.conn.TextDocumentDidSave(didSaveParams); err != nil {
logger.Logf("error reinitilizing clangd:", err)
logger.Logf("error reinitializing clangd:", err)
return err
}

Expand All @@ -159,18 +171,17 @@ func (r *SketchRebuilder) doRebuild(ctx context.Context, logger jsonrpc.Function
},
}
if err := ls.Clangd.conn.TextDocumentDidChange(didChangeParams); err != nil {
logger.Logf("error reinitilizing clangd:", err)
logger.Logf("error reinitializing clangd:", err)
return err
}

return nil
}

func (ls *INOLanguageServer) generateBuildEnvironment(ctx context.Context, logger jsonrpc.FunctionLogger) (bool, error) {
func (ls *INOLanguageServer) generateBuildEnvironment(ctx context.Context, fullBuild bool, buildPath *paths.Path, logger jsonrpc.FunctionLogger) (bool, error) {
// Extract all build information from language server status
ls.readLock(logger, false)
sketchRoot := ls.sketchRoot
buildPath := ls.buildPath
config := ls.config
type overridesFile struct {
Overrides map[string]string `json:"overrides"`
Expand Down Expand Up @@ -204,6 +215,7 @@ func (ls *INOLanguageServer) generateBuildEnvironment(ctx context.Context, logge
BuildPath: buildPath.String(),
CreateCompilationDatabaseOnly: true,
Verbose: true,
SkipLibrariesDiscovery: !fullBuild,
}
compileReqJson, _ := json.MarshalIndent(compileReq, "", " ")
logger.Logf("Running build with: %s", string(compileReqJson))
Expand Down Expand Up @@ -264,9 +276,12 @@ func (ls *INOLanguageServer) generateBuildEnvironment(ctx context.Context, logge
"--source-override", overridesJSON.String(),
"--build-path", buildPath.String(),
"--format", "json",
//"--clean",
sketchRoot.String(),
}
if !fullBuild {
args = append(args, "--skip-libraries-discovery")
}
args = append(args, sketchRoot.String())

cmd, err := executils.NewProcess(nil, args...)
if err != nil {
return false, errors.Errorf("running %s: %s", strings.Join(args, " "), err)
Expand Down
64 changes: 49 additions & 15 deletions ls/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type INOLanguageServer struct {
buildPath *paths.Path
buildSketchRoot *paths.Path
buildSketchCpp *paths.Path
fullBuildPath *paths.Path
sketchRoot *paths.Path
sketchName string
sketchMapper *sourcemapper.SketchMapper
Expand Down Expand Up @@ -136,9 +137,16 @@ func NewINOLanguageServer(stdin io.Reader, stdout io.Writer, config *Config) *IN
ls.buildSketchRoot = ls.buildPath.Join("sketch")
}

if tmp, err := paths.MkTempDir("", "arduino-language-server"); 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("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)
logger.Logf("Language server compile-commands: %s", ls.compileCommandsDir.Join("compile_commands.json"))

ls.IDE = NewIDELSPServer(logger, stdin, stdout, ls)
Expand Down Expand Up @@ -166,7 +174,7 @@ func (ls *INOLanguageServer) InitializeReqFromIDE(ctx context.Context, logger js
ls.sketchName = ls.sketchRoot.Base()
ls.buildSketchCpp = ls.buildSketchRoot.Join(ls.sketchName + ".ino.cpp")

if success, err := ls.generateBuildEnvironment(context.Background(), logger); err != nil {
if success, err := ls.generateBuildEnvironment(context.Background(), true, ls.buildPath, logger); err != nil {
logger.Logf("error starting clang: %s", err)
return
} else if !success {
Expand Down Expand Up @@ -209,10 +217,10 @@ func (ls *INOLanguageServer) InitializeReqFromIDE(ctx context.Context, logger js
clangInitializeParams.RootPath = ls.buildSketchRoot.String()
clangInitializeParams.RootURI = lsp.NewDocumentURIFromPath(ls.buildSketchRoot)
if clangInitializeResult, clangErr, err := ls.Clangd.conn.Initialize(ctx, &clangInitializeParams); err != nil {
logger.Logf("error initilizing clangd: %v", err)
logger.Logf("error initializing clangd: %v", err)
return
} else if clangErr != nil {
logger.Logf("error initilizing clangd: %v", clangErr.AsError())
logger.Logf("error initializing clangd: %v", clangErr.AsError())
return
} else {
logger.Logf("clangd successfully started: %s", string(lsp.EncodeMessage(clangInitializeResult)))
Expand Down Expand Up @@ -347,7 +355,7 @@ func (ls *INOLanguageServer) InitializeReqFromIDE(ctx context.Context, logger js
// TokenModifiers: []string{},
// },
// Range: false,
// Full: &lsp.SemantiTokenFullOptions{
// Full: &lsp.SemanticTokenFullOptions{
// Delta: true,
// },
// },
Expand Down Expand Up @@ -437,7 +445,7 @@ func (ls *INOLanguageServer) TextDocumentCompletionReqFromIDE(ctx context.Contex
if clangItem.Command != nil {
c := ls.clang2IdeCommand(logger, *clangItem.Command)
if c == nil {
continue // Skit item with unsupported command convertion
continue // Skit item with unsupported command conversion
}
ideCommand = c
}
Expand Down Expand Up @@ -595,7 +603,7 @@ func (ls *INOLanguageServer) TextDocumentDefinitionReqFromIDE(ctx context.Contex

func (ls *INOLanguageServer) TextDocumentTypeDefinitionReqFromIDE(ctx context.Context, logger jsonrpc.FunctionLogger, ideParams *lsp.TypeDefinitionParams) ([]lsp.Location, []lsp.LocationLink, *jsonrpc.ResponseError) {
// XXX: This capability is not advertised in the initialization message (clangd
// does not advetise it either, so maybe we should just not implement it)
// does not advertise it either, so maybe we should just not implement it)
ls.readLock(logger, true)
defer ls.readUnlock(logger)

Expand Down Expand Up @@ -974,9 +982,10 @@ func (ls *INOLanguageServer) TextDocumentDidOpenNotifFromIDE(logger jsonrpc.Func
return
}

// TODO: trigger `fullRebuild` if mail sketch file?
if ls.ideURIIsPartOfTheSketch(ideTextDocItem.URI) {
if !clangURI.AsPath().Exist() {
ls.triggerRebuildAndWait(logger)
ls.triggerRebuildAndWait(logger, true)
}
}

Expand Down Expand Up @@ -1026,7 +1035,7 @@ func (ls *INOLanguageServer) TextDocumentDidChangeNotifFromIDE(logger jsonrpc.Fu
ls.writeLock(logger, true)
defer ls.writeUnlock(logger)

ls.triggerRebuild()
ls.triggerRebuild(false)

logger.Logf("didChange(%s)", ideParams.TextDocument)
for _, change := range ideParams.ContentChanges {
Expand Down Expand Up @@ -1126,14 +1135,14 @@ func (ls *INOLanguageServer) TextDocumentDidSaveNotifFromIDE(logger jsonrpc.Func
// so we will not forward notification on saves in the sketch folder.
logger.Logf("notification is not forwarded to clang")

ls.triggerRebuild()
ls.triggerRebuild(false)
}

func (ls *INOLanguageServer) TextDocumentDidCloseNotifFromIDE(logger jsonrpc.FunctionLogger, ideParams *lsp.DidCloseTextDocumentParams) {
ls.writeLock(logger, true)
defer ls.writeUnlock(logger)

ls.triggerRebuild()
ls.triggerRebuild(false)

inoIdentifier := ideParams.TextDocument
if _, exist := ls.trackedIdeDocs[inoIdentifier.URI.AsPath().String()]; exist {
Expand Down Expand Up @@ -1172,6 +1181,31 @@ func (ls *INOLanguageServer) TextDocumentDidCloseNotifFromIDE(logger jsonrpc.Fun
}
}

func (ls *INOLanguageServer) FullBuildCompletedFromIDE(logger jsonrpc.FunctionLogger, params *DidCompleteBuildParams) {
ls.writeLock(logger, true)
defer ls.writeUnlock(logger)
ls.CopyBuildResults(logger, params.BuildOutputUri.AsPath(), true)
}

func (ls *INOLanguageServer) CopyBuildResults(logger jsonrpc.FunctionLogger, buildPath *paths.Path, fullRebuild bool) {
fromCommands := buildPath.Join("compile_commands.json")
toCommands := ls.compileCommandsDir.Join("compile_commands.json")
if err := fromCommands.CopyTo(toCommands); err != nil {
logger.Logf("ERROR: updating compile_commands: %s", err)
} else {
logger.Logf("Updated 'compile_commands'. Copied: %v to %v", fromCommands, toCommands)
}
if fullRebuild {
fromCache := buildPath.Join("libraries.cache")
toCache := ls.compileCommandsDir.Join("libraries.cache")
if err := fromCache.CopyTo(toCache); err != nil {
logger.Logf("ERROR: updating libraries.cache: %s", err)
} else {
logger.Logf("Updated 'libraries.cache'. Copied: %v to %v", fromCache, toCache)
}
}
}

func (ls *INOLanguageServer) PublishDiagnosticsNotifFromClangd(logger jsonrpc.FunctionLogger, clangParams *lsp.PublishDiagnosticsParams) {
ls.readLock(logger, false)
defer ls.readUnlock(logger)
Expand All @@ -1196,7 +1230,7 @@ func (ls *INOLanguageServer) PublishDiagnosticsNotifFromClangd(logger jsonrpc.Fu
ls.ideInoDocsWithDiagnostics[ideInoURI] = true
}

// .. and cleanup all previouse diagnostics that are no longer valid...
// .. and cleanup all previous diagnostics that are no longer valid...
for ideInoURI := range ls.ideInoDocsWithDiagnostics {
if _, ok := allIdeParams[ideInoURI]; ok {
continue
Expand All @@ -1218,7 +1252,7 @@ func (ls *INOLanguageServer) PublishDiagnosticsNotifFromClangd(logger jsonrpc.Fu
_ = json.Unmarshal(ideDiag.Code, &code)
switch code {
case "":
// Filter unkown non-string codes
// Filter unknown non-string codes
case "drv_unknown_argument_with_suggestion":
// Skip errors like: "Unknown argument '-mlongcalls'; did you mean '-mlong-calls'?"
case "drv_unknown_argument":
Expand Down Expand Up @@ -1296,7 +1330,7 @@ func (ls *INOLanguageServer) ideURIIsPartOfTheSketch(ideURI lsp.DocumentURI) boo
func (ls *INOLanguageServer) ProgressNotifFromClangd(logger jsonrpc.FunctionLogger, progress *lsp.ProgressParams) {
var token string
if err := json.Unmarshal(progress.Token, &token); err != nil {
logger.Logf("error decoding progess token: %s", err)
logger.Logf("error decoding progress token: %s", err)
return
}
switch value := progress.TryToDecodeWellKnownValues().(type) {
Expand Down Expand Up @@ -1470,7 +1504,7 @@ func (ls *INOLanguageServer) clang2IdeCommand(logger jsonrpc.FunctionLogger, cla

converted, err := json.Marshal(v)
if err != nil {
panic("Internal Error: json conversion of codeAcion command arguments")
panic("Internal Error: json conversion of codeAction command arguments")
}
ideCommand.Arguments[i] = converted
}
Expand All @@ -1496,7 +1530,7 @@ func (ls *INOLanguageServer) cpp2inoWorkspaceEdit(logger jsonrpc.FunctionLogger,
continue
}

// ...otherwise convert edits to the sketch.ino.cpp into multilpe .ino edits
// ...otherwise convert edits to the sketch.ino.cpp into multiple .ino edits
for _, edit := range edits {
inoURI, inoRange, inPreprocessed, err := ls.clang2IdeRangeAndDocumentURI(logger, editURI, edit.Range)
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions ls/lsp_server_ide.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewIDELSPServer(logger jsonrpc.FunctionLogger, in io.Reader, out io.Writer,
ls: ls,
}
server.conn = lsp.NewServer(in, out, server)
server.conn.RegisterCustomNotification("ino/didCompleteBuild", server.ArduinoBuildCompleted)
server.conn.SetLogger(&LSPLogger{
IncomingPrefix: "IDE --> LS",
OutgoingPrefix: "IDE <-- LS",
Expand Down Expand Up @@ -267,3 +268,17 @@ func (server *IDELSPServer) TextDocumentDidSave(logger jsonrpc.FunctionLogger, p
func (server *IDELSPServer) TextDocumentDidClose(logger jsonrpc.FunctionLogger, params *lsp.DidCloseTextDocumentParams) {
server.ls.TextDocumentDidCloseNotifFromIDE(logger, params)
}

// DidCompleteBuildParams is a custom notification from the Arduino IDE, sent
type DidCompleteBuildParams struct {
BuildOutputUri *lsp.DocumentURI `json:"buildOutputUri"`
}

func (server *IDELSPServer) ArduinoBuildCompleted(logger jsonrpc.FunctionLogger, raw json.RawMessage) {
var params DidCompleteBuildParams
if err := json.Unmarshal(raw, &params); err != nil {
logger.Logf("ERROR decoding FullBuildResult: %s", err)
} else {
server.ls.FullBuildCompletedFromIDE(logger, &params)
}
}