diff --git a/check/checkconfigurations/checkconfigurations.go b/check/checkconfigurations/checkconfigurations.go index d3a34142a..30ed67a0f 100644 --- a/check/checkconfigurations/checkconfigurations.go +++ b/check/checkconfigurations/checkconfigurations.go @@ -971,6 +971,21 @@ var configurations = []Type{ ErrorModes: []checkmode.Type{checkmode.Default}, CheckFunction: checkfunctions.LibraryFolderNameGTMaxLength, }, + { + ProjectType: projecttype.Library, + Category: "structure", + Subcategory: "", + ID: "", + Brief: "incorrect src folder case", + Description: "", + MessageTemplate: "Incorrect src folder case. This will cause the library to not be recognized on case-sensitive operating systems. See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-root-folder", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.Default}, + InfoModes: nil, + WarningModes: nil, + ErrorModes: []checkmode.Type{checkmode.Default}, + CheckFunction: checkfunctions.IncorrectLibrarySrcFolderNameCase, + }, { ProjectType: projecttype.Library, Category: "structure", @@ -1046,6 +1061,21 @@ var configurations = []Type{ ErrorModes: []checkmode.Type{checkmode.Default}, CheckFunction: checkfunctions.RecursiveLibraryWithUtilityFolder, }, + { + ProjectType: projecttype.Sketch, + Category: "structure", + Subcategory: "", + ID: "", + Brief: "incorrect src folder case", + Description: "", + MessageTemplate: "Incorrect src folder case. This will cause the source files under it to not be compiled on case-sensitive operating systems. See: https://arduino.github.io/arduino-cli/latest/sketch-specification/#src-subfolder", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.Default}, + InfoModes: nil, + WarningModes: []checkmode.Type{checkmode.Default}, + ErrorModes: []checkmode.Type{checkmode.Strict}, + CheckFunction: checkfunctions.IncorrectSketchSrcFolderNameCase, + }, { ProjectType: projecttype.Sketch, Category: "structure", diff --git a/check/checkfunctions/library.go b/check/checkfunctions/library.go index b03fbf921..b466bfef2 100644 --- a/check/checkfunctions/library.go +++ b/check/checkfunctions/library.go @@ -904,16 +904,8 @@ func LibraryPropertiesMisspelledOptionalField() (result checkresult.Type, output // LibraryInvalid checks whether the provided path is a valid library. func LibraryInvalid() (result checkresult.Type, output string) { - directoryListing, err := checkdata.LoadedLibrary().SourceDir.ReadDir() - if err != nil { - panic(err) - } - - directoryListing.FilterOutDirs() - for _, potentialHeaderFile := range directoryListing { - if library.HasHeaderFileValidExtension(potentialHeaderFile) { - return checkresult.Pass, "" - } + if library.ContainsHeaderFile(checkdata.LoadedLibrary().SourceDir) { + return checkresult.Pass, "" } return checkresult.Fail, "" @@ -1016,6 +1008,28 @@ func LibraryFolderNameGTMaxLength() (result checkresult.Type, output string) { return checkresult.Pass, "" } +// IncorrectLibrarySrcFolderNameCase checks for incorrect case of src subfolder name in recursive format libraries. +func IncorrectLibrarySrcFolderNameCase() (result checkresult.Type, output string) { + if library.ContainsMetadataFile(checkdata.ProjectPath()) && library.ContainsHeaderFile(checkdata.ProjectPath()) { + // Flat layout, so no special treatment of src subfolder. + return checkresult.NotRun, "" + } + + // The library is intended to have the recursive layout. + directoryListing, err := checkdata.ProjectPath().ReadDir() + if err != nil { + panic(err) + } + directoryListing.FilterDirs() + + path, found := containsIncorrectPathBaseCase(directoryListing, "src") + if found { + return checkresult.Fail, path.String() + } + + return checkresult.Pass, "" +} + // MisspelledExamplesFolderName checks for incorrectly spelled `examples` folder name. func MisspelledExamplesFolderName() (result checkresult.Type, output string) { directoryListing, err := checkdata.ProjectPath().ReadDir() diff --git a/check/checkfunctions/library_test.go b/check/checkfunctions/library_test.go index 39640afd4..53c41da2f 100644 --- a/check/checkfunctions/library_test.go +++ b/check/checkfunctions/library_test.go @@ -310,6 +310,16 @@ func TestLibraryFolderNameGTMaxLength(t *testing.T) { checkLibraryCheckFunction(LibraryFolderNameGTMaxLength, testTables, t) } +func TestIncorrectLibrarySrcFolderNameCase(t *testing.T) { + testTables := []libraryCheckFunctionTestTable{ + {"Flat, not precompiled", "Flat", checkresult.NotRun, ""}, + {"Incorrect case", "IncorrectSrcFolderNameCase", checkresult.Fail, ""}, + {"Correct case", "Recursive", checkresult.Pass, ""}, + } + + checkLibraryCheckFunction(IncorrectLibrarySrcFolderNameCase, testTables, t) +} + func TestMisspelledExamplesFolderName(t *testing.T) { testTables := []libraryCheckFunctionTestTable{ {"Correctly spelled", "ExamplesFolder", checkresult.Pass, ""}, diff --git a/check/checkfunctions/sketch.go b/check/checkfunctions/sketch.go index 31703c4d7..f3b931b92 100644 --- a/check/checkfunctions/sketch.go +++ b/check/checkfunctions/sketch.go @@ -25,6 +25,22 @@ import ( "github.com/arduino/arduino-check/project/sketch" ) +// IncorrectSketchSrcFolderNameCase checks for incorrect case of src subfolder name in recursive format libraries. +func IncorrectSketchSrcFolderNameCase() (result checkresult.Type, output string) { + directoryListing, err := checkdata.ProjectPath().ReadDir() + if err != nil { + panic(err) + } + directoryListing.FilterDirs() + + path, found := containsIncorrectPathBaseCase(directoryListing, "src") + if found { + return checkresult.Fail, path.String() + } + + return checkresult.Pass, "" +} + // ProhibitedCharactersInSketchFileName checks for prohibited characters in the sketch file names. func ProhibitedCharactersInSketchFileName() (result checkresult.Type, output string) { directoryListing, _ := checkdata.ProjectPath().ReadDir() diff --git a/check/checkfunctions/sketch_test.go b/check/checkfunctions/sketch_test.go index 11cc13565..825751a6d 100644 --- a/check/checkfunctions/sketch_test.go +++ b/check/checkfunctions/sketch_test.go @@ -60,6 +60,15 @@ func checkSketchCheckFunction(checkFunction Type, testTables []sketchCheckFuncti } } +func TestIncorrectSketchSrcFolderNameCase(t *testing.T) { + testTables := []sketchCheckFunctionTestTable{ + {"Incorrect case", "IncorrectSrcFolderNameCase", checkresult.Fail, ""}, + {"Correct case", "Valid", checkresult.Pass, ""}, + } + + checkSketchCheckFunction(IncorrectSketchSrcFolderNameCase, testTables, t) +} + func TestProhibitedCharactersInSketchFileName(t *testing.T) { testTables := []sketchCheckFunctionTestTable{ {"Has prohibited characters", "ProhibitedCharactersInFileName", checkresult.Fail, "^Prohibited CharactersInFileName.h$"}, diff --git a/check/checkfunctions/testdata/libraries/IncorrectSrcFolderNameCase/SRC/IncorrectSrcFolderNameCase.h b/check/checkfunctions/testdata/libraries/IncorrectSrcFolderNameCase/SRC/IncorrectSrcFolderNameCase.h new file mode 100644 index 000000000..e69de29bb diff --git a/check/checkfunctions/testdata/libraries/IncorrectSrcFolderNameCase/library.properties b/check/checkfunctions/testdata/libraries/IncorrectSrcFolderNameCase/library.properties new file mode 100644 index 000000000..d28f93232 --- /dev/null +++ b/check/checkfunctions/testdata/libraries/IncorrectSrcFolderNameCase/library.properties @@ -0,0 +1,9 @@ +name=IncorrectSrcFolderNameCase +version=1.0.0 +author=Cristian Maglie , Pippo Pluto +maintainer=Cristian Maglie +sentence=A library that makes coding a web server a breeze. +paragraph=Supports HTTP1.1 and you can do GET and POST. +category=Communication +url=http://example.com/ +architectures=avr diff --git a/check/checkfunctions/testdata/sketches/IncorrectSrcFolderNameCase/IncorrectSrcFolderNameCase.ino b/check/checkfunctions/testdata/sketches/IncorrectSrcFolderNameCase/IncorrectSrcFolderNameCase.ino new file mode 100644 index 000000000..660bdbccf --- /dev/null +++ b/check/checkfunctions/testdata/sketches/IncorrectSrcFolderNameCase/IncorrectSrcFolderNameCase.ino @@ -0,0 +1,2 @@ +void setup() {} +void loop() {} diff --git a/check/checkfunctions/testdata/sketches/IncorrectSrcFolderNameCase/SRC/src.cpp b/check/checkfunctions/testdata/sketches/IncorrectSrcFolderNameCase/SRC/src.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/check/checkfunctions/testdata/sketches/SrcSubfolder/SrcSubfolder.ino b/check/checkfunctions/testdata/sketches/SrcSubfolder/SrcSubfolder.ino new file mode 100644 index 000000000..660bdbccf --- /dev/null +++ b/check/checkfunctions/testdata/sketches/SrcSubfolder/SrcSubfolder.ino @@ -0,0 +1,2 @@ +void setup() {} +void loop() {} diff --git a/check/checkfunctions/testdata/sketches/SrcSubfolder/src/src.cpp b/check/checkfunctions/testdata/sketches/SrcSubfolder/src/src.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/project/library/library.go b/project/library/library.go index 37f13e984..da1b88128 100644 --- a/project/library/library.go +++ b/project/library/library.go @@ -17,6 +17,8 @@ package library import ( + "fmt" + "github.com/arduino/go-paths-helper" ) @@ -35,6 +37,30 @@ func HasHeaderFileValidExtension(filePath *paths.Path) bool { return hasHeaderFileValidExtension } +// ContainsHeaderFile checks whether the provided path contains a file with valid header extension. +func ContainsHeaderFile(searchPath *paths.Path) bool { + if searchPath.NotExist() { + panic(fmt.Sprintf("Error: provided path %s does not exist.", searchPath)) + } + if searchPath.IsNotDir() { + panic(fmt.Sprintf("Error: provided path %s is not a directory.", searchPath)) + } + + directoryListing, err := searchPath.ReadDir() + if err != nil { + panic(err) + } + + directoryListing.FilterOutDirs() + for _, potentialHeaderFile := range directoryListing { + if HasHeaderFileValidExtension(potentialHeaderFile) { + return true + } + } + + return false +} + // See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata var metadataFilenames = map[string]struct{}{ "library.properties": empty, @@ -49,6 +75,30 @@ func IsMetadataFile(filePath *paths.Path) bool { return false } +// ContainsMetadataFile checks whether the provided path contains an Arduino library metadata file. +func ContainsMetadataFile(searchPath *paths.Path) bool { + if searchPath.NotExist() { + panic(fmt.Sprintf("Error: provided path %s does not exist.", searchPath)) + } + if searchPath.IsNotDir() { + panic(fmt.Sprintf("Error: provided path %s is not a directory.", searchPath)) + } + + directoryListing, err := searchPath.ReadDir() + if err != nil { + panic(err) + } + + directoryListing.FilterOutDirs() + for _, potentialMetadataFile := range directoryListing { + if IsMetadataFile(potentialMetadataFile) { + return true + } + } + + return false +} + // See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-examples var examplesFolderValidNames = map[string]struct{}{ "examples": empty, diff --git a/project/library/library_test.go b/project/library/library_test.go new file mode 100644 index 000000000..e6155c998 --- /dev/null +++ b/project/library/library_test.go @@ -0,0 +1,41 @@ +// This file is part of arduino-check. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-check. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package library + +import ( + "os" + "testing" + + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/assert" +) + +var testDataPath *paths.Path + +func init() { + workingDirectory, _ := os.Getwd() + testDataPath = paths.New(workingDirectory, "testdata") +} + +func TestContainsHeaderFile(t *testing.T) { + assert.True(t, ContainsHeaderFile(testDataPath.Join("ContainsHeaderFile"))) + assert.False(t, ContainsHeaderFile(testDataPath.Join("ContainsNoHeaderFile"))) +} + +func TestContainsMetadataFile(t *testing.T) { + assert.True(t, ContainsMetadataFile(testDataPath.Join("ContainsMetadataFile"))) + assert.False(t, ContainsMetadataFile(testDataPath.Join("ContainsNoMetadataFile"))) +} diff --git a/project/library/testdata/ContainsHeaderFile/foo.h b/project/library/testdata/ContainsHeaderFile/foo.h new file mode 100644 index 000000000..e69de29bb diff --git a/project/library/testdata/ContainsMetadataFile/library.properties b/project/library/testdata/ContainsMetadataFile/library.properties new file mode 100644 index 000000000..e69de29bb diff --git a/project/library/testdata/ContainsNoHeaderFile/foo.bar b/project/library/testdata/ContainsNoHeaderFile/foo.bar new file mode 100644 index 000000000..e69de29bb diff --git a/project/library/testdata/ContainsNoMetadataFile/foo.bar b/project/library/testdata/ContainsNoMetadataFile/foo.bar new file mode 100644 index 000000000..e69de29bb