From 32ce6490b70ae762c41167758f7eb06a5c053ebc Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Sep 2020 13:18:04 +0200 Subject: [PATCH 1/2] Let ExecRecipe return the command executed This prepares for building a compilation database later. The returned command is not currently used anywhere yet, so this commit should not change behaviour. --- legacy/builder/builder_utils/utils.go | 11 ++++++----- legacy/builder/phases/linker.go | 2 +- legacy/builder/phases/sizer.go | 2 +- legacy/builder/recipe_runner.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/legacy/builder/builder_utils/utils.go b/legacy/builder/builder_utils/utils.go index 0c6e991762f..0307f09fb71 100644 --- a/legacy/builder/builder_utils/utils.go +++ b/legacy/builder/builder_utils/utils.go @@ -249,7 +249,7 @@ func compileFileWithRecipe(ctx *types.Context, sourcePath *paths.Path, source *p return nil, errors.WithStack(err) } if !objIsUpToDate { - _, _, err = ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) + _, _, _, err = ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) if err != nil { return nil, errors.WithStack(err) } @@ -480,7 +480,7 @@ func ArchiveCompiledFiles(ctx *types.Context, buildPath *paths.Path, archiveFile properties.SetPath(constants.BUILD_PROPERTIES_ARCHIVE_FILE_PATH, archiveFilePath) properties.SetPath(constants.BUILD_PROPERTIES_OBJECT_FILE, objectFile) - if _, _, err := ExecRecipe(ctx, properties, constants.RECIPE_AR_PATTERN, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show); err != nil { + if _, _, _, err := ExecRecipe(ctx, properties, constants.RECIPE_AR_PATTERN, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show); err != nil { return nil, errors.WithStack(err) } } @@ -488,14 +488,15 @@ func ArchiveCompiledFiles(ctx *types.Context, buildPath *paths.Path, archiveFile return archiveFilePath, nil } -func ExecRecipe(ctx *types.Context, buildProperties *properties.Map, recipe string, removeUnsetProperties bool, stdout int, stderr int) ([]byte, []byte, error) { +func ExecRecipe(ctx *types.Context, buildProperties *properties.Map, recipe string, removeUnsetProperties bool, stdout int, stderr int) (*exec.Cmd, []byte, []byte, error) { // See util.ExecCommand for stdout/stderr arguments command, err := PrepareCommandForRecipe(ctx, buildProperties, recipe, removeUnsetProperties) if err != nil { - return nil, nil, errors.WithStack(err) + return nil, nil, nil, errors.WithStack(err) } - return utils.ExecCommand(ctx, command, stdout, stderr) + outbytes, errbytes, err := utils.ExecCommand(ctx, command, stdout, stderr) + return command, outbytes, errbytes, err } const COMMANDLINE_LIMIT = 30000 diff --git a/legacy/builder/phases/linker.go b/legacy/builder/phases/linker.go index 32bcaf8bafb..3319c3241b0 100644 --- a/legacy/builder/phases/linker.go +++ b/legacy/builder/phases/linker.go @@ -67,7 +67,7 @@ func link(ctx *types.Context, objectFiles paths.PathList, coreDotARelPath *paths properties.Set(constants.BUILD_PROPERTIES_ARCHIVE_FILE_PATH, coreArchiveFilePath.String()) properties.Set(constants.BUILD_PROPERTIES_OBJECT_FILES, objectFileList) - _, _, err := builder_utils.ExecRecipe(ctx, properties, constants.RECIPE_C_COMBINE_PATTERN, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) + _, _, _, err := builder_utils.ExecRecipe(ctx, properties, constants.RECIPE_C_COMBINE_PATTERN, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) return err } diff --git a/legacy/builder/phases/sizer.go b/legacy/builder/phases/sizer.go index 2d5b055c5bb..74b0fec0846 100644 --- a/legacy/builder/phases/sizer.go +++ b/legacy/builder/phases/sizer.go @@ -112,7 +112,7 @@ func checkSize(ctx *types.Context, buildProperties *properties.Map) error { } func execSizeRecipe(ctx *types.Context, properties *properties.Map) (textSize int, dataSize int, eepromSize int, resErr error) { - out, _, err := builder_utils.ExecRecipe(ctx, properties, constants.RECIPE_SIZE_PATTERN, false /* stdout */, utils.Capture /* stderr */, utils.Show) + _, out, _, err := builder_utils.ExecRecipe(ctx, properties, constants.RECIPE_SIZE_PATTERN, false /* stdout */, utils.Capture /* stderr */, utils.Show) if err != nil { resErr = errors.New("Error while determining sketch size: " + err.Error()) return diff --git a/legacy/builder/recipe_runner.go b/legacy/builder/recipe_runner.go index 45be3218b78..7464843df6e 100644 --- a/legacy/builder/recipe_runner.go +++ b/legacy/builder/recipe_runner.go @@ -47,7 +47,7 @@ func (s *RecipeByPrefixSuffixRunner) Run(ctx *types.Context) error { if ctx.DebugLevel >= 10 { logger.Fprintln(os.Stdout, constants.LOG_LEVEL_DEBUG, constants.MSG_RUNNING_RECIPE, recipe) } - _, _, err := builder_utils.ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) + _, _, _, err := builder_utils.ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) if err != nil { return errors.WithStack(err) } From 7f4f32e6338bc6405292ebac19be9f4b46e7ac0f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Sep 2020 18:43:13 +0200 Subject: [PATCH 2/2] Draft: Support generating a compile_commands.json file This is still very rough and unfinished. --- arduino/builder/compilation_database.go | 73 +++++++++++++++++++++++++ commands/compile/compile.go | 7 +++ legacy/builder/builder.go | 5 ++ legacy/builder/builder_utils/utils.go | 8 ++- legacy/builder/types/context.go | 4 ++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 arduino/builder/compilation_database.go diff --git a/arduino/builder/compilation_database.go b/arduino/builder/compilation_database.go new file mode 100644 index 00000000000..50828c3add4 --- /dev/null +++ b/arduino/builder/compilation_database.go @@ -0,0 +1,73 @@ +package builder + +import ( + "encoding/json" + "fmt" + "github.com/arduino/go-paths-helper" + "io/ioutil" + "os" + "os/exec" +) + +type compilationCommand struct { + Directory string `json:"directory"` + Arguments []string `json:"arguments"` + File string `json:"file"` +} + +type CompilationDatabase struct { + contents []compilationCommand + filename *paths.Path +} + +func NewCompilationDatabase(filename *paths.Path) *CompilationDatabase { + return &CompilationDatabase{ + filename: filename, + } +} + +func (db *CompilationDatabase) UpdateFile(complete bool) { + // TODO: Read any existing file and use its contents for any + // kept files, or any files not in db.contents if !complete. + fmt.Printf("Writing compilation database to \"%s\"...\n", db.filename.String()) + + contents := db.contents + jsonContents, err := json.MarshalIndent(contents, "", " ") + if err != nil { + fmt.Printf("Error serializing compilation database: %s", err) + return + } + err = ioutil.WriteFile(db.filename.String(), jsonContents, 0644) + if err != nil { + fmt.Printf("Error writing compilation database: %s", err) + } +} + +func (db *CompilationDatabase) dirForCommand(command *exec.Cmd) string { + // This mimics what Cmd.Run also does: Use Dir if specified, + // current directory otherwise + if command.Dir != "" { + return command.Dir + } else { + dir, err := os.Getwd() + if err != nil { + fmt.Printf("Error getting current directory for compilation database: %s", err) + return "" + } + return dir + } +} + +func (db *CompilationDatabase) ReplaceEntry(filename *paths.Path, command *exec.Cmd) { + entry := compilationCommand{ + Directory: db.dirForCommand(command), + Arguments: command.Args, + File: filename.String(), + } + + db.contents = append(db.contents, entry) +} + +func (db *CompilationDatabase) KeepEntry(filename *paths.Path) { + // TODO +} diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 2d220e1b84c..50d18dfe49a 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + clibuilder "github.com/arduino/arduino-cli/arduino/builder" "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino/cores/packagemanager" "github.com/arduino/arduino-cli/arduino/sketches" @@ -119,6 +120,12 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W builderCtx.PackageManager = pm builderCtx.FQBN = fqbn builderCtx.SketchLocation = sketch.FullPath + // TODO: Make optional using commandline option? + if !req.GetDryRun() { + builderCtx.CompilationDatabase = clibuilder.NewCompilationDatabase( + sketch.FullPath.Join("compile_commands.json"), + ) + } // FIXME: This will be redundant when arduino-builder will be part of the cli builderCtx.HardwareDirs = configuration.HardwareDirectories() diff --git a/legacy/builder/builder.go b/legacy/builder/builder.go index e9aafb0b73c..9ef70220af0 100644 --- a/legacy/builder/builder.go +++ b/legacy/builder/builder.go @@ -101,6 +101,11 @@ func (s *Builder) Run(ctx *types.Context) error { mainErr := runCommands(ctx, commands) + // TODO: Make proper step? + if ctx.CompilationDatabase != nil { + ctx.CompilationDatabase.UpdateFile(mainErr != nil) + } + commands = []types.Command{ &PrintUsedAndNotUsedLibraries{SketchError: mainErr != nil}, diff --git a/legacy/builder/builder_utils/utils.go b/legacy/builder/builder_utils/utils.go index 0307f09fb71..c26c960a1f3 100644 --- a/legacy/builder/builder_utils/utils.go +++ b/legacy/builder/builder_utils/utils.go @@ -249,11 +249,17 @@ func compileFileWithRecipe(ctx *types.Context, sourcePath *paths.Path, source *p return nil, errors.WithStack(err) } if !objIsUpToDate { - _, _, _, err = ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) + command, _, _, err := ExecRecipe(ctx, properties, recipe, false /* stdout */, utils.ShowIfVerbose /* stderr */, utils.Show) + if ctx.CompilationDatabase != nil { + ctx.CompilationDatabase.ReplaceEntry(source, command) + } if err != nil { return nil, errors.WithStack(err) } } else if ctx.Verbose { + if ctx.CompilationDatabase != nil { + ctx.CompilationDatabase.KeepEntry(source) + } logger.Println(constants.LOG_LEVEL_INFO, constants.MSG_USING_PREVIOUS_COMPILED_FILE, objectFile) } diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go index bcd24047550..ffa722168b9 100644 --- a/legacy/builder/types/context.go +++ b/legacy/builder/types/context.go @@ -19,6 +19,7 @@ import ( "io" "strings" + "github.com/arduino/arduino-cli/arduino/builder" "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino/cores/packagemanager" "github.com/arduino/arduino-cli/arduino/libraries" @@ -158,6 +159,9 @@ type Context struct { // Out and Err stream to redirect all Exec commands ExecStdout io.Writer ExecStderr io.Writer + + // Compilation Database to build/update + CompilationDatabase *builder.CompilationDatabase } func (ctx *Context) ExtractBuildOptions() *properties.Map {