diff --git a/commands/commands_test.go b/commands/commands_test.go index d1f37f343b2..288e52df807 100644 --- a/commands/commands_test.go +++ b/commands/commands_test.go @@ -390,14 +390,40 @@ func TestCompileCommands(t *testing.T) { require.Contains(t, string(d), "Sketch created") // Build sketch for arduino:avr:uno - exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:uno", currSketchbookDir.Join("Test1").String()) + test1 := currSketchbookDir.Join("Test1").String() + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:uno", test1) require.Zero(t, exitCode, "exit code") require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New(test1).Join("Test1.arduino.avr.uno.hex").Exist()) // Build sketch for arduino:avr:nano (without options) - exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", currSketchbookDir.Join("Test1").String()) + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", test1) require.Zero(t, exitCode, "exit code") require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New(test1).Join("Test1.arduino.avr.nano.hex").Exist()) + + // Build sketch with --output path + require.NoError(t, os.Chdir(tmp)) + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", "-o", "test", test1) + require.Zero(t, exitCode, "exit code") + require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New("test.hex").Exist()) + + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", "-o", "test2.hex", test1) + require.Zero(t, exitCode, "exit code") + require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New("test2.hex").Exist()) + require.NoError(t, paths.New(tmp, "anothertest").MkdirAll()) + + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", "-o", "anothertest/test", test1) + require.Zero(t, exitCode, "exit code") + require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New("anothertest", "test.hex").Exist()) + + exitCode, d = executeWithArgs(t, "compile", "-b", "arduino:avr:nano", "-o", tmp+"/anothertest/test2", test1) + require.Zero(t, exitCode, "exit code") + require.Contains(t, string(d), "Sketch uses") + require.True(t, paths.New("anothertest", "test2.hex").Exist()) } func TestCoreCommands(t *testing.T) { diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 27cade9108d..a5d544c740c 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -47,25 +47,38 @@ func InitCommand() *cobra.Command { Args: cobra.MaximumNArgs(1), Run: run, } - command.Flags().StringVarP(&flags.fqbn, "fqbn", "b", "", + command.Flags().StringVarP( + &flags.fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:avr:uno") - command.Flags().BoolVar(&flags.showProperties, "show-properties", false, + command.Flags().BoolVar( + &flags.showProperties, "show-properties", false, "Show all build properties used instead of compiling.") - command.Flags().BoolVar(&flags.preprocess, "preprocess", false, + command.Flags().BoolVar( + &flags.preprocess, "preprocess", false, "Print preprocessed code to stdout instead of compiling.") - command.Flags().StringVar(&flags.buildCachePath, "build-cache-path", "", + command.Flags().StringVar( + &flags.buildCachePath, "build-cache-path", "", "Builds of 'core.a' are saved into this path to be cached and reused.") - command.Flags().StringVar(&flags.buildPath, "build-path", "", + command.Flags().StringVarP( + &flags.exportFile, "output", "o", "", + "Filename of the compile output.") + command.Flags().StringVar( + &flags.buildPath, "build-path", "", "Path where to save compiled files. If omitted, a directory will be created in the default temporary path of your OS.") - command.Flags().StringSliceVar(&flags.buildProperties, "build-properties", []string{}, + command.Flags().StringSliceVar( + &flags.buildProperties, "build-properties", []string{}, "List of custom build properties separated by commas. Or can be used multiple times for multiple properties.") - command.Flags().StringVar(&flags.warnings, "warnings", "none", + command.Flags().StringVar( + &flags.warnings, "warnings", "none", `Optional, can be "none", "default", "more" and "all". Defaults to "none". Used to tell gcc which warning level to use (-W flag).`) - command.Flags().BoolVarP(&flags.verbose, "verbose", "v", false, + command.Flags().BoolVarP( + &flags.verbose, "verbose", "v", false, "Optional, turns on verbose mode.") - command.Flags().BoolVar(&flags.quiet, "quiet", false, + command.Flags().BoolVar( + &flags.quiet, "quiet", false, "Optional, supresses almost every output.") - command.Flags().StringVar(&flags.vidPid, "vid-pid", "", + command.Flags().StringVar( + &flags.vidPid, "vid-pid", "", "When specified, VID/PID specific build properties are used, if boards supports them.") return command } @@ -81,6 +94,7 @@ var flags struct { verbose bool // Turns on verbose mode. quiet bool // Suppresses almost every output. vidPid string // VID/PID specific build properties. + exportFile string // The compiled binary is written to this file } func run(cmd *cobra.Command, args []string) { @@ -95,21 +109,18 @@ func run(cmd *cobra.Command, args []string) { os.Exit(commands.ErrGeneric) } - fqbn := flags.fqbn - if fqbn == "" && sketch != nil { - fqbn = sketch.Metadata.CPU.Fqbn + if flags.fqbn == "" && sketch != nil { + flags.fqbn = sketch.Metadata.CPU.Fqbn } - if fqbn == "" { + if flags.fqbn == "" { formatter.PrintErrorMessage("No Fully Qualified Board Name provided.") os.Exit(commands.ErrGeneric) } - fqbnParts := strings.Split(fqbn, ":") - if len(fqbnParts) < 3 || len(fqbnParts) > 4 { + fqbn, err := cores.ParseFQBN(flags.fqbn) + if err != nil { formatter.PrintErrorMessage("Fully Qualified Board Name has incorrect format.") os.Exit(commands.ErrBadArgument) } - packageName := fqbnParts[0] - coreName := fqbnParts[1] pm := commands.InitPackageManager() @@ -133,25 +144,20 @@ func run(cmd *cobra.Command, args []string) { } targetPlatform := pm.FindPlatform(&packagemanager.PlatformReference{ - Package: packageName, - PlatformArchitecture: coreName, + Package: fqbn.Package, + PlatformArchitecture: fqbn.PlatformArch, }) if targetPlatform == nil || pm.GetInstalledPlatformRelease(targetPlatform) == nil { errorMessage := fmt.Sprintf( "\"%[1]s:%[2]s\" platform is not installed, please install it by running \""+ - commands.AppName+" core install %[1]s:%[2]s\".", packageName, coreName) + commands.AppName+" core install %[1]s:%[2]s\".", fqbn.Package, fqbn.PlatformArch) formatter.PrintErrorMessage(errorMessage) os.Exit(commands.ErrCoreConfig) } ctx := &types.Context{} ctx.PackageManager = pm - if parsedFqbn, err := cores.ParseFQBN(fqbn); err != nil { - formatter.PrintError(err, "Error parsing FQBN.") - os.Exit(commands.ErrBadArgument) - } else { - ctx.FQBN = parsedFqbn - } + ctx.FQBN = fqbn ctx.SketchLocation = paths.New(sketch.FullPath) // FIXME: This will be redundant when arduino-builder will be part of the cli @@ -242,12 +248,28 @@ func run(cmd *cobra.Command, args []string) { // FIXME: Make a function to obtain these info... outputPath := ctx.BuildProperties.ExpandPropsInString("{build.path}/{recipe.output.tmp_file}") ext := filepath.Ext(outputPath) + // FIXME: Make a function to produce a better name... - fqbn = strings.Replace(fqbn, ":", ".", -1) + // Make the filename without the FQBN configs part + fqbn.Configs = properties.NewMap() + fqbnSuffix := strings.Replace(fqbn.String(), ":", ".", -1) + + var exportPath *paths.Path + var exportFile string + if flags.exportFile == "" { + exportPath = paths.New(sketch.FullPath) + exportFile = sketch.Name + "." + fqbnSuffix + } else { + exportPath = paths.New(flags.exportFile).Parent() + exportFile = paths.New(flags.exportFile).Base() + if strings.HasSuffix(exportFile, ext) { + exportFile = exportFile[:len(exportFile)-len(ext)] + } + } // Copy .hex file to sketch directory srcHex := paths.New(outputPath) - dstHex := paths.New(sketch.FullPath).Join(sketch.Name + "." + fqbn + ext) + dstHex := exportPath.Join(exportFile + ext) logrus.WithField("from", srcHex).WithField("to", dstHex).Print("copying sketch build output") if err = srcHex.CopyTo(dstHex); err != nil { formatter.PrintError(err, "Error copying output file.") @@ -256,7 +278,7 @@ func run(cmd *cobra.Command, args []string) { // Copy .elf file to sketch directory srcElf := paths.New(outputPath[:len(outputPath)-3] + "elf") - dstElf := paths.New(sketch.FullPath).Join(sketch.Name + "." + fqbn + ".elf") + dstElf := exportPath.Join(exportFile + ".elf") logrus.WithField("from", srcElf).WithField("to", dstElf).Print("copying sketch build output") if err = srcElf.CopyTo(dstElf); err != nil { formatter.PrintError(err, "Error copying elf file.") diff --git a/commands/upload/upload.go b/commands/upload/upload.go index ce14bd40d8a..47bf8833130 100644 --- a/commands/upload/upload.go +++ b/commands/upload/upload.go @@ -45,22 +45,30 @@ func InitCommand() *cobra.Command { Args: cobra.MaximumNArgs(1), Run: run, } - uploadCommand.Flags().StringVarP(&flags.fqbn, "fqbn", "b", "", + uploadCommand.Flags().StringVarP( + &flags.fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:avr:uno") - uploadCommand.Flags().StringVarP(&flags.port, "port", "p", "", + uploadCommand.Flags().StringVarP( + &flags.port, "port", "p", "", "Upload port, e.g.: COM10 or /dev/ttyACM0") - uploadCommand.Flags().BoolVarP(&flags.verify, "verify", "t", false, + uploadCommand.Flags().StringVarP( + &flags.importFile, "input", "i", "", + "Input file to be uploaded.") + uploadCommand.Flags().BoolVarP( + &flags.verify, "verify", "t", false, "Verify uploaded binary after the upload.") - uploadCommand.Flags().BoolVarP(&flags.verbose, "verbose", "v", false, + uploadCommand.Flags().BoolVarP( + &flags.verbose, "verbose", "v", false, "Optional, turns on verbose mode.") return uploadCommand } var flags struct { - fqbn string - port string - verbose bool - verify bool + fqbn string + port string + verbose bool + verify bool + importFile string } func run(command *cobra.Command, args []string) { @@ -81,42 +89,28 @@ func run(command *cobra.Command, args []string) { os.Exit(commands.ErrBadCall) } - fqbn := flags.fqbn - if fqbn == "" && sketch != nil { - fqbn = sketch.Metadata.CPU.Fqbn + if flags.fqbn == "" && sketch != nil { + flags.fqbn = sketch.Metadata.CPU.Fqbn } - if fqbn == "" { + if flags.fqbn == "" { formatter.PrintErrorMessage("No Fully Qualified Board Name provided.") os.Exit(commands.ErrBadCall) } - fqbnParts := strings.Split(fqbn, ":") - if len(fqbnParts) < 3 || len(fqbnParts) > 4 { - formatter.PrintErrorMessage("Fully Qualified Board Name has incorrect format.") + fqbn, err := cores.ParseFQBN(flags.fqbn) + if err != nil { + formatter.PrintError(err, "Invalid FQBN.") os.Exit(commands.ErrBadCall) } pm := commands.InitPackageManager() - // Find target board - board, err := pm.FindBoardWithFQBN(fqbn) + // Find target board and board properties + _, _, board, boardProperties, _, err := pm.ResolveFQBN(fqbn) if err != nil { formatter.PrintError(err, "Invalid FQBN.") os.Exit(commands.ErrBadCall) } - // Create board configuration - var boardProperties *properties.Map - if len(fqbnParts) == 3 { - boardProperties = board.Properties - } else { - if props, err := board.GeneratePropertiesForConfiguration(fqbnParts[3]); err != nil { - formatter.PrintError(err, "Invalid FQBN.") - os.Exit(commands.ErrBadCall) - } else { - boardProperties = props - } - } - // Load programmer tool uploadToolID, have := boardProperties.GetOk("upload.tool") if !have || uploadToolID == "" { @@ -194,12 +188,27 @@ func run(command *cobra.Command, args []string) { } // Set path to compiled binary - // FIXME: refactor this should be made into a function - fqbn = strings.Replace(fqbn, ":", ".", -1) - uploadProperties.Set("build.path", sketch.FullPath) - uploadProperties.Set("build.project_name", sketch.Name+"."+fqbn) + // Make the filename without the FQBN configs part + fqbn.Configs = properties.NewMap() + fqbnSuffix := strings.Replace(fqbn.String(), ":", ".", -1) ext := filepath.Ext(uploadProperties.ExpandPropsInString("{recipe.output.tmp_file}")) - if _, err := os.Stat(filepath.Join(sketch.FullPath, sketch.Name+"."+fqbn+ext)); err != nil { + + var importPath *paths.Path + var importFile string + if flags.importFile == "" { + importPath = paths.New(sketch.FullPath) + importFile = sketch.Name + "." + fqbnSuffix + } else { + importPath = paths.New(flags.importFile).Parent() + importFile = paths.New(flags.importFile).Base() + if strings.HasSuffix(importFile, ext) { + importFile = importFile[:len(importFile)-len(ext)] + } + } + + uploadProperties.SetPath("build.path", importPath) + uploadProperties.Set("build.project_name", importFile) + if _, err := os.Stat(filepath.Join(sketch.FullPath, importFile+ext)); err != nil { if os.IsNotExist(err) { formatter.PrintErrorMessage("Compiled sketch not found. Please compile first.") } else {