diff --git a/arduino/globals/globals.go b/arduino/globals/globals.go index a7802d6dff2..b58d00f3e0e 100644 --- a/arduino/globals/globals.go +++ b/arduino/globals/globals.go @@ -18,9 +18,13 @@ package globals var ( empty struct{} + // MainFileValidExtension is the extension that must be used for files in new sketches + MainFileValidExtension string = ".ino" + // MainFileValidExtensions lists valid extensions for a sketch file MainFileValidExtensions = map[string]struct{}{ - ".ino": empty, + MainFileValidExtension: empty, + // .pde extension is deprecated and must not be used for new sketches ".pde": empty, } diff --git a/arduino/libraries/loader.go b/arduino/libraries/loader.go index 8e8462536e4..70e738d3fdd 100644 --- a/arduino/libraries/loader.go +++ b/arduino/libraries/loader.go @@ -19,6 +19,7 @@ import ( "fmt" "strings" + "github.com/arduino/arduino-cli/arduino/sketches" "github.com/arduino/go-paths-helper" properties "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" @@ -172,7 +173,8 @@ func addExamplesToPathList(examplesPath *paths.Path, list *paths.PathList) error return err } for _, file := range files { - if isExample(file) { + _, err := sketches.NewSketchFromPath(file) + if err == nil { list.Add(file) } else if file.IsDir() { if err := addExamplesToPathList(file, list); err != nil { @@ -182,9 +184,3 @@ func addExamplesToPathList(examplesPath *paths.Path, list *paths.PathList) error } return nil } - -// isExample returns true if examplePath contains an example -func isExample(examplePath *paths.Path) bool { - mainIno := examplePath.Join(examplePath.Base() + ".ino") - return mainIno.Exist() && mainIno.IsNotDir() -} diff --git a/arduino/sketches/sketches.go b/arduino/sketches/sketches.go index fef80969b08..d219e936f62 100644 --- a/arduino/sketches/sketches.go +++ b/arduino/sketches/sketches.go @@ -20,15 +20,17 @@ import ( "fmt" "github.com/arduino/arduino-cli/arduino/builder" + "github.com/arduino/arduino-cli/arduino/globals" "github.com/arduino/go-paths-helper" "github.com/pkg/errors" ) // Sketch is a sketch for Arduino type Sketch struct { - Name string - FullPath *paths.Path - Metadata *Metadata + Name string + MainFileExtension string + FullPath *paths.Path + Metadata *Metadata } // Metadata is the kind of data associated to a project such as the connected board @@ -52,14 +54,32 @@ func NewSketchFromPath(path *paths.Path) (*Sketch, error) { if !path.IsDir() { path = path.Parent() } - sketchFile := path.Join(path.Base() + ".ino") - if !sketchFile.Exist() { - return nil, errors.Errorf("no valid sketch found in %s: missing %s", path, sketchFile.Base()) + + var mainSketchFile *paths.Path + for ext := range globals.MainFileValidExtensions { + candidateSketchMainFile := path.Join(path.Base() + ext) + if candidateSketchMainFile.Exist() { + if mainSketchFile == nil { + mainSketchFile = candidateSketchMainFile + } else { + return nil, errors.Errorf("multiple main sketch files found (%v, %v)", + mainSketchFile, + candidateSketchMainFile, + ) + } + } + } + + if mainSketchFile == nil { + sketchFile := path.Join(path.Base() + globals.MainFileValidExtension) + return nil, errors.Errorf("no valid sketch found in %s: missing %s", path, sketchFile) } + sketch := &Sketch{ - FullPath: path, - Name: path.Base(), - Metadata: &Metadata{}, + FullPath: path, + MainFileExtension: mainSketchFile.Ext(), + Name: path.Base(), + Metadata: &Metadata{}, } sketch.ImportMetadata() return sketch, nil @@ -108,3 +128,20 @@ func (s *Sketch) BuildPath() (*paths.Path, error) { } return builder.GenBuildPath(s.FullPath), nil } + +// CheckForPdeFiles returns all files ending with .pde extension +// in dir, this is mainly used to warn the user that these files +// must be changed to .ino extension. +// When .pde files won't be supported anymore this function must be removed. +func CheckForPdeFiles(sketch *paths.Path) []*paths.Path { + if sketch.IsNotDir() { + sketch = sketch.Parent() + } + + files, err := sketch.ReadDirRecursive() + if err != nil { + return []*paths.Path{} + } + files.FilterSuffix(".pde") + return files +} diff --git a/arduino/sketches/sketches_test.go b/arduino/sketches/sketches_test.go index 65a30c37cea..49ea771e4fd 100644 --- a/arduino/sketches/sketches_test.go +++ b/arduino/sketches/sketches_test.go @@ -53,9 +53,59 @@ func TestSketchBuildPath(t *testing.T) { require.NoError(t, err) require.Contains(t, buildPath.String(), "arduino-sketch-") + // Verifies sketch path is returned if sketch has .pde extension + sketchPath = paths.New("testdata", "SketchPde") + sketch, err = NewSketchFromPath(sketchPath) + require.NoError(t, err) + require.NotNil(t, sketch) + buildPath, err = sketch.BuildPath() + require.NoError(t, err) + require.Contains(t, buildPath.String(), "arduino-sketch-") + + // Verifies error is returned if there are multiple main files + sketchPath = paths.New("testdata", "SketchMultipleMainFiles") + sketch, err = NewSketchFromPath(sketchPath) + require.Nil(t, sketch) + require.Error(t, err, "multiple main sketch files found") + // Verifies error is returned if sketch path is not set sketch = &Sketch{} buildPath, err = sketch.BuildPath() require.Nil(t, buildPath) require.Error(t, err, "sketch path is empty") } + +func TestCheckForPdeFiles(t *testing.T) { + sketchPath := paths.New("testdata", "Sketch1") + files := CheckForPdeFiles(sketchPath) + require.Empty(t, files) + + sketchPath = paths.New("testdata", "SketchPde") + files = CheckForPdeFiles(sketchPath) + require.Len(t, files, 1) + require.Equal(t, sketchPath.Join("SketchPde.pde"), files[0]) + + sketchPath = paths.New("testdata", "SketchMultipleMainFiles") + files = CheckForPdeFiles(sketchPath) + require.Len(t, files, 1) + require.Equal(t, sketchPath.Join("SketchMultipleMainFiles.pde"), files[0]) + + sketchPath = paths.New("testdata", "Sketch1", "Sketch1.ino") + files = CheckForPdeFiles(sketchPath) + require.Empty(t, files) + + sketchPath = paths.New("testdata", "SketchPde", "SketchPde.pde") + files = CheckForPdeFiles(sketchPath) + require.Len(t, files, 1) + require.Equal(t, sketchPath.Parent().Join("SketchPde.pde"), files[0]) + + sketchPath = paths.New("testdata", "SketchMultipleMainFiles", "SketchMultipleMainFiles.ino") + files = CheckForPdeFiles(sketchPath) + require.Len(t, files, 1) + require.Equal(t, sketchPath.Parent().Join("SketchMultipleMainFiles.pde"), files[0]) + + sketchPath = paths.New("testdata", "SketchMultipleMainFiles", "SketchMultipleMainFiles.pde") + files = CheckForPdeFiles(sketchPath) + require.Len(t, files, 1) + require.Equal(t, sketchPath.Parent().Join("SketchMultipleMainFiles.pde"), files[0]) +} diff --git a/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.ino b/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.ino new file mode 100644 index 00000000000..5054c040393 --- /dev/null +++ b/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.ino @@ -0,0 +1,3 @@ + +void setup() {} +void loop() {} diff --git a/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.pde b/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.pde new file mode 100644 index 00000000000..5054c040393 --- /dev/null +++ b/arduino/sketches/testdata/SketchMultipleMainFiles/SketchMultipleMainFiles.pde @@ -0,0 +1,3 @@ + +void setup() {} +void loop() {} diff --git a/arduino/sketches/testdata/SketchPde/SketchPde.pde b/arduino/sketches/testdata/SketchPde/SketchPde.pde new file mode 100644 index 00000000000..5054c040393 --- /dev/null +++ b/arduino/sketches/testdata/SketchPde/SketchPde.pde @@ -0,0 +1,3 @@ + +void setup() {} +void loop() {} diff --git a/cli/compile/compile.go b/cli/compile/compile.go index e77b3fb5740..4b095cdf592 100644 --- a/cli/compile/compile.go +++ b/cli/compile/compile.go @@ -21,6 +21,7 @@ import ( "encoding/json" "os" + "github.com/arduino/arduino-cli/arduino/sketches" "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/configuration" @@ -127,6 +128,15 @@ func run(cmd *cobra.Command, args []string) { } sketchPath := initSketchPath(path) + + // .pde files are still supported but deprecated, this warning urges the user to rename them + if files := sketches.CheckForPdeFiles(sketchPath); len(files) > 0 { + feedback.Error("Sketches with .pde extension are deprecated, please rename the following files to .ino:") + for _, f := range files { + feedback.Error(f) + } + } + // We must read this from settings since the value is set when the binding is accessed from viper, // accessing it from cobra would only read it if the flag is explicitly set by the user and ignore // the config file and the env vars. diff --git a/cli/sketch/archive.go b/cli/sketch/archive.go index 65eff2d8a98..b44f9990ab8 100644 --- a/cli/sketch/archive.go +++ b/cli/sketch/archive.go @@ -19,10 +19,12 @@ import ( "context" "os" + "github.com/arduino/arduino-cli/arduino/sketches" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/commands/sketch" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -53,11 +55,19 @@ func initArchiveCommand() *cobra.Command { func runArchiveCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino sketch archive`") - sketchPath := "" + sketchPath := "." if len(args) >= 1 { sketchPath = args[0] } + // .pde files are still supported but deprecated, this warning urges the user to rename them + if files := sketches.CheckForPdeFiles(paths.New(sketchPath)); len(files) > 0 { + feedback.Error("Sketches with .pde extension are deprecated, please rename the following files to .ino:") + for _, f := range files { + feedback.Error(f) + } + } + archivePath := "" if len(args) == 2 { archivePath = args[1] diff --git a/cli/upload/upload.go b/cli/upload/upload.go index fc75999ca3b..6476f8a8a9f 100644 --- a/cli/upload/upload.go +++ b/cli/upload/upload.go @@ -19,6 +19,7 @@ import ( "context" "os" + "github.com/arduino/arduino-cli/arduino/sketches" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/instance" @@ -83,6 +84,14 @@ func run(command *cobra.Command, args []string) { } sketchPath := initSketchPath(path) + // .pde files are still supported but deprecated, this warning urges the user to rename them + if files := sketches.CheckForPdeFiles(sketchPath); len(files) > 0 { + feedback.Error("Sketches with .pde extension are deprecated, please rename the following files to .ino:") + for _, f := range files { + feedback.Error(f) + } + } + if _, err := upload.Upload(context.Background(), &rpc.UploadReq{ Instance: instance, Fqbn: fqbn, diff --git a/commands/sketch/archive.go b/commands/sketch/archive.go index d5b77fd792b..d702489a83b 100644 --- a/commands/sketch/archive.go +++ b/commands/sketch/archive.go @@ -23,6 +23,7 @@ import ( "path/filepath" "strings" + "github.com/arduino/arduino-cli/arduino/sketches" rpc "github.com/arduino/arduino-cli/rpc/commands" paths "github.com/arduino/go-paths-helper" ) @@ -37,27 +38,17 @@ func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.Archive sketchPath = paths.New(".") } - sketchPath, err := sketchPath.Clean().Abs() + sketch, err := sketches.NewSketchFromPath(sketchPath) if err != nil { - return nil, fmt.Errorf("Error getting absolute sketch path %v", err) + return nil, err } - // Get the sketch name and make sketchPath point to the ino file - if sketchPath.IsDir() { - sketchName = sketchPath.Base() - sketchPath = sketchPath.Join(sketchName + ".ino") - } else if sketchPath.Ext() == ".ino" { - sketchName = strings.TrimSuffix(sketchPath.Base(), ".ino") - } - - // Checks if it's really a sketch - if sketchPath.NotExist() { - return nil, fmt.Errorf("specified path is not a sketch: %v", sketchPath.String()) - } + sketchPath = sketch.FullPath + sketchName = sketch.Name archivePath := paths.New(req.ArchivePath) if archivePath == nil { - archivePath = sketchPath.Parent().Parent() + archivePath = sketchPath.Parent() } archivePath, err = archivePath.Clean().Abs() @@ -76,7 +67,7 @@ func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.Archive return nil, fmt.Errorf("archive already exists") } - filesToZip, err := sketchPath.Parent().ReadDirRecursive() + filesToZip, err := sketchPath.ReadDirRecursive() if err != nil { return nil, fmt.Errorf("Error retrieving sketch files: %v", err) } @@ -94,7 +85,7 @@ func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.Archive for _, f := range filesToZip { if !req.IncludeBuildDir { - filePath, err := sketchPath.Parent().Parent().RelTo(f) + filePath, err := sketchPath.Parent().RelTo(f) if err != nil { return nil, fmt.Errorf("Error calculating relative file path: %v", err) } @@ -107,7 +98,7 @@ func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.Archive // We get the parent path since we want the archive to unpack as a folder. // If we don't do this the archive would contain all the sketch files as top level. - err = addFileToSketchArchive(zipWriter, f, sketchPath.Parent().Parent()) + err = addFileToSketchArchive(zipWriter, f, sketchPath.Parent()) if err != nil { return nil, fmt.Errorf("Error adding file to archive: %v", err) } diff --git a/commands/upload/upload.go b/commands/upload/upload.go index bd55c7ecd5d..8da6876c287 100644 --- a/commands/upload/upload.go +++ b/commands/upload/upload.go @@ -26,6 +26,7 @@ import ( bldr "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/globals" "github.com/arduino/arduino-cli/arduino/serialutils" "github.com/arduino/arduino-cli/arduino/sketches" "github.com/arduino/arduino-cli/commands" @@ -452,7 +453,7 @@ func determineBuildPathAndSketchName(importFile, importDir string, sketch *sketc // Case 4: only sketch specified. In this case we use the generated build path // and the given sketch name. - return bldr.GenBuildPath(sketch.FullPath), sketch.Name + ".ino", nil + return bldr.GenBuildPath(sketch.FullPath), sketch.Name + sketch.MainFileExtension, nil } func detectSketchNameFromBuildPath(buildPath *paths.Path) (string, error) { @@ -462,11 +463,13 @@ func detectSketchNameFromBuildPath(buildPath *paths.Path) (string, error) { } if absBuildPath, err := buildPath.Abs(); err == nil { - candidateName := absBuildPath.Base() + ".ino" - f := files.Clone() - f.FilterPrefix(candidateName + ".") - if f.Len() > 0 { - return candidateName, nil + for ext := range globals.MainFileValidExtensions { + candidateName := absBuildPath.Base() + ext + f := files.Clone() + f.FilterPrefix(candidateName + ".") + if f.Len() > 0 { + return candidateName, nil + } } } @@ -479,7 +482,7 @@ func detectSketchNameFromBuildPath(buildPath *paths.Path) (string, error) { // Sometimes we may have particular files like: // Blink.ino.with_bootloader.bin - if filepath.Ext(name) != ".ino" { + if _, ok := globals.MainFileValidExtensions[filepath.Ext(name)]; !ok { // just ignore those files continue } diff --git a/docs/sketch-specification.md b/docs/sketch-specification.md index fb1a59060e6..f1f2dd9c292 100644 --- a/docs/sketch-specification.md +++ b/docs/sketch-specification.md @@ -18,7 +18,10 @@ Support for sketch folder names starting with a number was added in Arduino IDE ### Primary sketch file -Every sketch must contain a .ino or .pde file with a file name matching the sketch root folder name. +Every sketch must contain a `.ino` file with a file name matching the sketch root folder name. + +`.pde` is also supported but **deprecated** and will be removed in the future, using the `.ino` extension is strongly +recommended. ### Additional code files @@ -28,7 +31,7 @@ The following extensions are supported: - .ino - [Arduino language](https://www.arduino.cc/reference/en/) files. - .pde - Alternate extension for Arduino language files. This file extension is also used by Processing sketches. .ino - is recommended to avoid confusion. + is recommended to avoid confusion. **`.pde` extension is deprecated and will be removed in the future.** - .cpp - C++ files. - .c - C Files. - .S - Assembly language files. diff --git a/test/test_compile.py b/test/test_compile.py index 8b5337e02cf..aee5b0c8a1d 100644 --- a/test/test_compile.py +++ b/test/test_compile.py @@ -16,6 +16,7 @@ import platform import tempfile import hashlib +import shutil from pathlib import Path import simplejson as json @@ -629,3 +630,68 @@ def test_compile_with_fully_precompiled_library(run_command, data_dir): result = run_command(f"compile -b {fqbn} {sketch_folder} -v") assert result.ok assert "Skipping dependencies detection for precompiled library Arduino_TensorFlowLite" in result.stdout + + +def test_compile_sketch_with_pde_extension(run_command, data_dir): + # Init the environment explicitly + assert run_command("update") + + # Install core to compile + assert run_command("core install arduino:avr@1.8.3") + + sketch_name = "CompilePdeSketch" + sketch_path = Path(data_dir, sketch_name) + fqbn = "arduino:avr:uno" + + # Create a test sketch + assert run_command(f"sketch new {sketch_path}") + + # Renames sketch file to pde + sketch_file = Path(sketch_path, f"{sketch_name}.ino").rename(sketch_path / f"{sketch_name}.pde") + + # Build sketch from folder + res = run_command(f"compile --clean -b {fqbn} {sketch_path}") + assert res.ok + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino:" in res.stderr + assert str(sketch_file) in res.stderr + + # Build sketch from file + res = run_command(f"compile --clean -b {fqbn} {sketch_file}") + assert res.ok + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino" in res.stderr + assert str(sketch_file) in res.stderr + + +def test_compile_sketch_with_multiple_main_files(run_command, data_dir): + # Init the environment explicitly + assert run_command("update") + + # Install core to compile + assert run_command("core install arduino:avr@1.8.3") + + sketch_name = "CompileSketchMultipleMainFiles" + sketch_path = Path(data_dir, sketch_name) + fqbn = "arduino:avr:uno" + + # Create a test sketch + assert run_command(f"sketch new {sketch_path}") + + # Copy .ino sketch file to .pde + sketch_ino_file = Path(sketch_path, f"{sketch_name}.ino") + sketch_pde_file = Path(sketch_path / f"{sketch_name}.pde") + shutil.copyfile(sketch_ino_file, sketch_pde_file) + + # Build sketch from folder + res = run_command(f"compile --clean -b {fqbn} {sketch_path}") + assert res.failed + assert "Error during build: opening sketch: multiple main sketch files found" in res.stderr + + # Build sketch from .ino file + res = run_command(f"compile --clean -b {fqbn} {sketch_ino_file}") + assert res.failed + assert "Error during build: opening sketch: multiple main sketch files found" in res.stderr + + # Build sketch from .pde file + res = run_command(f"compile --clean -b {fqbn} {sketch_pde_file}") + assert res.failed + assert "Error during build: opening sketch: multiple main sketch files found" in res.stderr diff --git a/test/test_debug.py b/test/test_debug.py index 9c3d4079396..30fcfd09777 100644 --- a/test/test_debug.py +++ b/test/test_debug.py @@ -37,3 +37,27 @@ def test_debugger_starts(run_command, data_dir): programmer = "atmel_ice" # Starts debugger assert run_command(f"debug -b {fqbn} -P {programmer} {sketch_path} --info") + + +def test_debugger_with_pde_sketch_starts(run_command, data_dir): + assert run_command("update") + + # Install core + assert run_command("core install arduino:samd") + + # Create sketch for testing + sketch_name = "DebuggerPdeSketchStartTest" + sketch_path = Path(data_dir, sketch_name) + fqbn = "arduino:samd:mkr1000" + + assert run_command(f"sketch new {sketch_path}") + + # Renames sketch file to pde + Path(sketch_path, f"{sketch_name}.ino").rename(sketch_path / f"{sketch_name}.pde") + + # Build sketch + assert run_command(f"compile -b {fqbn} {sketch_path}") + + programmer = "atmel_ice" + # Starts debugger + assert run_command(f"debug -b {fqbn} -P {programmer} {sketch_path} --info") diff --git a/test/test_lib.py b/test/test_lib.py index 77e03cd13c5..5a164b95f05 100644 --- a/test/test_lib.py +++ b/test/test_lib.py @@ -621,3 +621,36 @@ def test_install_with_zip_path_multiple_libraries(run_command, downloads_dir, da # Verifies library are installed assert wifi_install_dir.exists() assert ble_install_dir.exists() + + +def test_lib_examples(run_command, data_dir): + assert run_command("update") + + assert run_command("lib install Arduino_JSON@0.1.0") + + res = run_command("lib examples Arduino_JSON --format json") + assert res.ok + data = json.loads(res.stdout) + assert len(data) == 1 + examples = data[0]["examples"] + + assert str(Path(data_dir, "libraries", "Arduino_JSON", "examples", "JSONArray")) in examples + assert str(Path(data_dir, "libraries", "Arduino_JSON", "examples", "JSONKitchenSink")) in examples + assert str(Path(data_dir, "libraries", "Arduino_JSON", "examples", "JSONObject")) in examples + + +def test_lib_examples_with_pde_file(run_command, data_dir): + assert run_command("update") + + assert run_command("lib install Encoder@1.4.1") + + res = run_command("lib examples Encoder --format json") + assert res.ok + data = json.loads(res.stdout) + assert len(data) == 1 + examples = data[0]["examples"] + + assert str(Path(data_dir, "libraries", "Encoder", "examples", "Basic")) in examples + assert str(Path(data_dir, "libraries", "Encoder", "examples", "NoInterrupts")) in examples + assert str(Path(data_dir, "libraries", "Encoder", "examples", "SpeedTest")) in examples + assert str(Path(data_dir, "libraries", "Encoder", "examples", "TwoKnobs")) in examples diff --git a/test/test_sketch.py b/test/test_sketch.py index 34ac263a71c..d3fb4126faa 100644 --- a/test/test_sketch.py +++ b/test/test_sketch.py @@ -818,3 +818,31 @@ def test_sketch_archive_absolute_sketch_path_with_absolute_zip_path_and_name_wit verify_zip_contains_sketch_including_build_dir(archive_files) archive.close() + + +def test_sketch_archive_with_pde_main_file(run_command, copy_sketch, working_dir): + sketch_name = "sketch_pde_main_file" + sketch_dir = copy_sketch(sketch_name) + sketch_file = Path(sketch_dir, f"{sketch_name}.pde") + res = run_command("sketch archive", sketch_dir) + assert res.ok + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino" in res.stderr + assert str(sketch_file.relative_to(sketch_dir)) in res.stderr + + archive = zipfile.ZipFile(f"{working_dir}/{sketch_name}.zip") + archive_files = archive.namelist() + + assert f"{sketch_name}/{sketch_name}.pde" in archive_files + + archive.close() + + +def test_sketch_archive_with_multiple_main_files(run_command, copy_sketch, working_dir): + sketch_name = "sketch_multiple_main_files" + sketch_dir = copy_sketch(sketch_name) + sketch_file = Path(sketch_dir, f"{sketch_name}.pde") + res = run_command("sketch archive", sketch_dir) + assert res.failed + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino" in res.stderr + assert str(sketch_file.relative_to(sketch_dir)) in res.stderr + assert "Error archiving: multiple main sketch files found" in res.stderr diff --git a/test/test_upload.py b/test/test_upload.py index 60d0149dde4..7f8d272a822 100644 --- a/test/test_upload.py +++ b/test/test_upload.py @@ -13,6 +13,8 @@ # software without disclosing the source code of your own applications. To purchase # a commercial license, send an email to license@arduino.cc. import os +import shutil +import json from pathlib import Path import pytest @@ -195,3 +197,143 @@ def test_compile_and_upload_combo_with_custom_build_path(run_command, data_dir, assert f"Compile {sketch_name} for {board.fqbn} successful" in traces assert f"Upload {sketch_path} on {board.fqbn} started" in traces assert "Upload successful" in traces + + +def test_compile_and_upload_combo_sketch_with_pde_extension(run_command, data_dir, detected_boards, wait_for_board): + assert run_command("update") + + sketch_name = "CompileAndUploadPdeSketch" + sketch_path = Path(data_dir, sketch_name) + + # Create a test sketch + assert run_command(f"sketch new {sketch_path}") + + # Renames sketch file to pde + sketch_file = Path(sketch_path, f"{sketch_name}.ino").rename(sketch_path / f"{sketch_name}.pde") + + for board in detected_boards: + # Install core + core = ":".join(board.fqbn.split(":")[:2]) + assert run_command(f"core install {core}") + + # Build sketch and upload from folder + wait_for_board() + res = run_command(f"compile --clean -b {board.fqbn} -u -p {board.address} {sketch_path}") + assert res.ok + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino" in res.stderr + assert str(sketch_file) in res.stderr + + # Build sketch and upload from file + wait_for_board() + res = run_command(f"compile --clean -b {board.fqbn} -u -p {board.address} {sketch_file}") + assert res.ok + assert "Sketches with .pde extension are deprecated, please rename the following files to .ino" in res.stderr + assert str(sketch_file) in res.stderr + + +def test_upload_sketch_with_pde_extension(run_command, data_dir, detected_boards, wait_for_board): + assert run_command("update") + + sketch_name = "UploadPdeSketch" + sketch_path = Path(data_dir, sketch_name) + + # Create a test sketch + assert run_command(f"sketch new {sketch_path}") + + # Renames sketch file to pde + sketch_file = Path(sketch_path, f"{sketch_name}.ino").rename(sketch_path / f"{sketch_name}.pde") + + for board in detected_boards: + # Install core + core = ":".join(board.fqbn.split(":")[:2]) + assert run_command(f"core install {core}") + + # Compile sketch first + res = run_command(f"compile --clean -b {board.fqbn} {sketch_path} --format json") + assert res.ok + data = json.loads(res.stdout) + build_dir = Path(data["builder_result"]["build_path"]) + + # Upload from sketch folder + wait_for_board() + assert run_command(f"upload -b {board.fqbn} -p {board.address} {sketch_path}") + + # Upload from sketch file + wait_for_board() + assert run_command(f"upload -b {board.fqbn} -p {board.address} {sketch_file}") + + wait_for_board() + res = run_command(f"upload -b {board.fqbn} -p {board.address} --input-dir {build_dir}") + assert ( + "Sketches with .pde extension are deprecated, please rename the following files to .ino:" not in res.stderr + ) + + # Upload from binary file + wait_for_board() + # We don't need a specific file when using the --input-file flag to upload since + # it's just used to calculate the directory, so it's enough to get a random file + # that's inside that directory + binary_file = next(build_dir.glob(f"{sketch_name}.pde.*")) + res = run_command(f"upload -b {board.fqbn} -p {board.address} --input-file {binary_file}") + assert ( + "Sketches with .pde extension are deprecated, please rename the following files to .ino:" not in res.stderr + ) + + +def test_upload_with_input_dir_containing_multiple_binaries(run_command, data_dir, detected_boards, wait_for_board): + # This tests verifies the behaviour outlined in this issue: + # https://github.com/arduino/arduino-cli/issues/765#issuecomment-699678646 + assert run_command("update") + + # Create a two different sketches + sketch_one_name = "UploadMultipleBinariesSketchOne" + sketch_one_path = Path(data_dir, sketch_one_name) + assert run_command(f"sketch new {sketch_one_path}") + + sketch_two_name = "UploadMultipleBinariesSketchTwo" + sketch_two_path = Path(data_dir, sketch_two_name) + assert run_command(f"sketch new {sketch_two_path}") + + for board in detected_boards: + # Install core + core = ":".join(board.fqbn.split(":")[:2]) + assert run_command(f"core install {core}") + + # Compile both sketches and copy binaries in the same directory same build directory + res = run_command(f"compile --clean -b {board.fqbn} {sketch_one_path} --format json") + assert res.ok + data = json.loads(res.stdout) + build_dir_one = Path(data["builder_result"]["build_path"]) + res = run_command(f"compile --clean -b {board.fqbn} {sketch_two_path} --format json") + assert res.ok + data = json.loads(res.stdout) + build_dir_two = Path(data["builder_result"]["build_path"]) + + # Copy binaries to same folder + binaries_dir = Path(data_dir, "build", "BuiltBinaries") + shutil.copytree(build_dir_one, binaries_dir, dirs_exist_ok=True) + shutil.copytree(build_dir_two, binaries_dir, dirs_exist_ok=True) + + wait_for_board() + # Verifies upload fails because multiple binaries are found + res = run_command(f"upload -b {board.fqbn} -p {board.address} --input-dir {binaries_dir}") + assert res.failed + assert ( + "Error during Upload: " + + "retrieving build artifacts: " + + "autodetect build artifact: " + + "multiple build artifacts found:" + in res.stderr + ) + + # Copy binaries to folder with same name of a sketch + binaries_dir = Path(data_dir, "build", "UploadMultipleBinariesSketchOne") + shutil.copytree(build_dir_one, binaries_dir, dirs_exist_ok=True) + shutil.copytree(build_dir_two, binaries_dir, dirs_exist_ok=True) + + wait_for_board() + # Verifies upload is successful using the binaries with the same name of the containing folder + res = run_command(f"upload -b {board.fqbn} -p {board.address} --input-dir {binaries_dir}") + assert ( + "Sketches with .pde extension are deprecated, please rename the following files to .ino:" not in res.stderr + ) diff --git a/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.ino b/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.ino new file mode 100644 index 00000000000..2263c4bcd9e --- /dev/null +++ b/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.ino @@ -0,0 +1,3 @@ +void setup() { } + +void loop() { } \ No newline at end of file diff --git a/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.pde b/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.pde new file mode 100644 index 00000000000..2263c4bcd9e --- /dev/null +++ b/test/testdata/sketch_multiple_main_files/sketch_multiple_main_files.pde @@ -0,0 +1,3 @@ +void setup() { } + +void loop() { } \ No newline at end of file diff --git a/test/testdata/sketch_pde_main_file/sketch_pde_main_file.pde b/test/testdata/sketch_pde_main_file/sketch_pde_main_file.pde new file mode 100644 index 00000000000..2263c4bcd9e --- /dev/null +++ b/test/testdata/sketch_pde_main_file/sketch_pde_main_file.pde @@ -0,0 +1,3 @@ +void setup() { } + +void loop() { } \ No newline at end of file