@@ -20,6 +20,7 @@ import (
20
20
"fmt"
21
21
"io"
22
22
"net/url"
23
+ "path/filepath"
23
24
"strings"
24
25
"time"
25
26
@@ -32,6 +33,7 @@ import (
32
33
rpc "github.com/arduino/arduino-cli/rpc/commands"
33
34
paths "github.com/arduino/go-paths-helper"
34
35
properties "github.com/arduino/go-properties-orderedmap"
36
+ "github.com/pkg/errors"
35
37
"github.com/sirupsen/logrus"
36
38
"go.bug.st/serial"
37
39
)
@@ -240,43 +242,19 @@ func runProgramAction(pm *packagemanager.PackageManager,
240
242
uploadProperties .Set ("program.verify" , uploadProperties .Get ("program.params.noverify" ))
241
243
}
242
244
243
- var importPath * paths.Path
244
245
if ! burnBootloader {
245
- if importFile != "" {
246
- importFilePath := paths .New (importFile )
247
- if ! importFilePath .Exist () {
248
- return fmt .Errorf ("binary file not found in %s" , importFilePath )
249
- }
250
- importPath = importFilePath .Parent ()
251
- // In general, the binary file extension (like .bin or .hex or even .zip) are already written explicitly in
252
- // the core recipes inside platform.txt. This why the CLI removes it before setting the build.project_name
253
- // property.
254
- importFileName := strings .TrimSuffix (importFilePath .Base (), importFilePath .Ext ())
255
- uploadProperties .SetPath ("build.path" , importPath )
256
- uploadProperties .Set ("build.project_name" , importFileName )
257
- } else {
258
- if sketch == nil {
259
- return fmt .Errorf (("no sketch specified" ))
260
- }
261
- if importDir != "" {
262
- importPath = paths .New (importDir )
263
- } else {
264
- // TODO: Create a function to obtain importPath from sketch
265
- importPath = sketch .FullPath
266
- // Add FQBN (without configs part) to export path
267
- fqbnSuffix := strings .Replace (fqbn .StringWithoutConfig (), ":" , "." , - 1 )
268
- importPath = importPath .Join ("build" ).Join (fqbnSuffix )
269
- }
270
-
271
- if ! importPath .Exist () {
272
- return fmt .Errorf ("compiled sketch not found in %s" , importPath )
273
- }
274
- if ! importPath .IsDir () {
275
- return fmt .Errorf ("expected compiled sketch in directory %s, but is a file instead" , importPath )
276
- }
277
- uploadProperties .SetPath ("build.path" , importPath )
278
- uploadProperties .Set ("build.project_name" , sketch .Name + ".ino" )
246
+ importPath , sketchName , err := determineBuildPathAndSketchName (importFile , importDir , sketch , fqbn )
247
+ if err != nil {
248
+ return errors .Errorf ("retrieving build artifacts: %s" , err )
249
+ }
250
+ if ! importPath .Exist () {
251
+ return fmt .Errorf ("compiled sketch not found in %s" , importPath )
252
+ }
253
+ if ! importPath .IsDir () {
254
+ return fmt .Errorf ("expected compiled sketch in directory %s, but is a file instead" , importPath )
279
255
}
256
+ uploadProperties .SetPath ("build.path" , importPath )
257
+ uploadProperties .Set ("build.project_name" , sketchName )
280
258
}
281
259
282
260
// If not using programmer perform some action required
@@ -463,3 +441,109 @@ func waitForNewSerialPort() (string, error) {
463
441
464
442
return "" , nil
465
443
}
444
+
445
+ func determineBuildPathAndSketchName (importFile , importDir string , sketch * sketches.Sketch , fqbn * cores.FQBN ) (* paths.Path , string , error ) {
446
+ // In general, compiling a sketch will produce a set of files that are
447
+ // named as the sketch but have different extensions, for example Sketch.ino
448
+ // may produce: Sketch.ino.bin; Sketch.ino.hex; Sketch.ino.zip; etc...
449
+ // These files are created together in the build directory and anyone of
450
+ // them may be required for upload.
451
+
452
+ // The upload recipes are already written using the 'build.project_name' property
453
+ // concatenated with an explicit extension. To perform the upload we should now
454
+ // determine the project name (e.g. 'sketch.ino) and set the 'build.project_name'
455
+ // property accordingly, together with the 'build.path' property to point to the
456
+ // directory containing the build artifacts.
457
+
458
+ // Case 1: importFile flag has been specified
459
+ if importFile != "" {
460
+ if importDir != "" {
461
+ return nil , "" , fmt .Errorf ("importFile and importDir cannot be used together" )
462
+ }
463
+
464
+ // We have a path like "path/to/my/build/SketchName.ino.bin". We are going to
465
+ // ignore the extension and set:
466
+ // - "build.path" as "path/to/my/build"
467
+ // - "build.project_name" as "SketchName.ino"
468
+
469
+ importFilePath := paths .New (importFile )
470
+ if ! importFilePath .Exist () {
471
+ return nil , "" , fmt .Errorf ("binary file not found in %s" , importFilePath )
472
+ }
473
+ return importFilePath .Parent (), strings .TrimSuffix (importFilePath .Base (), importFilePath .Ext ()), nil
474
+ }
475
+
476
+ if importDir != "" {
477
+ // Case 2: importDir flag with a sketch
478
+ if sketch != nil {
479
+ // In this case we have both the build path and the sketch name given,
480
+ // so we just return them as-is
481
+ return paths .New (importDir ), sketch .Name + ".ino" , nil
482
+ }
483
+
484
+ // Case 3: importDir flag without a sketch
485
+
486
+ // In this case we have a build path but the sketch name is not given, we may
487
+ // try to determine the sketch name by applying some euristics to the build folder.
488
+ // - "build.path" as importDir
489
+ // - "build.project_name" after trying to autodetect it from the build folder.
490
+ buildPath := paths .New (importDir )
491
+ sketchName , err := detectSketchNameFromBuildPath (buildPath )
492
+ if err != nil {
493
+ return nil , "" , errors .Errorf ("autodetect build artifact: %s" , err )
494
+ }
495
+ return buildPath , sketchName , nil
496
+ }
497
+
498
+ // Case 4: nothing given...
499
+ if sketch == nil {
500
+ return nil , "" , fmt .Errorf ("no sketch or build directory/file specified" )
501
+ }
502
+
503
+ // Case 5: only sketch specified. In this case we use the default sketch build path
504
+ // and the given sketch name.
505
+
506
+ // TODO: Create a function to obtain importPath from sketch
507
+ // Add FQBN (without configs part) to export path
508
+ if fqbn == nil {
509
+ return nil , "" , fmt .Errorf ("missing FQBN" )
510
+ }
511
+ fqbnSuffix := strings .Replace (fqbn .StringWithoutConfig (), ":" , "." , - 1 )
512
+ return sketch .FullPath .Join ("build" ).Join (fqbnSuffix ), sketch .Name + ".ino" , nil
513
+ }
514
+
515
+ func detectSketchNameFromBuildPath (buildPath * paths.Path ) (string , error ) {
516
+ files , err := buildPath .ReadDir ()
517
+ if err != nil {
518
+ return "" , err
519
+ }
520
+
521
+ candidateName := ""
522
+ var candidateFile * paths.Path
523
+ for _ , file := range files {
524
+ // Build artifacts are usually names as "Blink.ino.hex" or "Blink.ino.bin".
525
+ // Extract the "Blink.ino" part
526
+ name := strings .TrimSuffix (file .Base (), file .Ext ())
527
+
528
+ // Sometimes we may have particular files like:
529
+ // Blink.ino.with_bootloader.bin
530
+ if filepath .Ext (name ) != ".ino" {
531
+ // just ignore those files
532
+ continue
533
+ }
534
+
535
+ if candidateName == "" {
536
+ candidateName = name
537
+ candidateFile = file
538
+ }
539
+
540
+ if candidateName != name {
541
+ return "" , errors .Errorf ("multiple build artifacts found: '%s' and '%s'" , candidateFile , file )
542
+ }
543
+ }
544
+
545
+ if candidateName == "" {
546
+ return "" , errors .New ("could not find a valid build artifact" )
547
+ }
548
+ return candidateName , nil
549
+ }
0 commit comments