diff --git a/check/checkconfigurations/checkconfigurations.go b/check/checkconfigurations/checkconfigurations.go index bd4dca2d8..714fb0a60 100644 --- a/check/checkconfigurations/checkconfigurations.go +++ b/check/checkconfigurations/checkconfigurations.go @@ -806,6 +806,21 @@ var configurations = []Type{ ErrorModes: nil, CheckFunction: checkfunctions.LibraryHasSubmodule, }, + { + ProjectType: projecttype.Library, + Category: "structure", + Subcategory: "general", + ID: "", + Brief: "symlink", + Description: "", + MessageTemplate: "Symlink(s) found at {{.}}. These block acceptance to the Arduino Library Manager index.", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: []checkmode.Type{checkmode.Default}, + ErrorModes: []checkmode.Type{checkmode.LibraryManagerSubmission, checkmode.LibraryManagerIndexed}, + CheckFunction: checkfunctions.LibraryContainsSymlinks, + }, { ProjectType: projecttype.Sketch, Category: "structure", diff --git a/check/checkfunctions/library.go b/check/checkfunctions/library.go index 023f69e68..52b9b6dba 100644 --- a/check/checkfunctions/library.go +++ b/check/checkfunctions/library.go @@ -19,6 +19,7 @@ package checkfunctions import ( "net/http" + "os" "path/filepath" "strings" @@ -864,6 +865,33 @@ func LibraryHasSubmodule() (result checkresult.Type, output string) { return checkresult.Pass, "" } +// LibraryContainsSymlinks checks if the library folder contains symbolic links. +func LibraryContainsSymlinks() (result checkresult.Type, output string) { + projectPathListing, err := checkdata.ProjectPath().ReadDirRecursive() + if err != nil { + panic(err) + } + projectPathListing.FilterOutDirs() + + symlinkPaths := []string{} + for _, projectPathItem := range projectPathListing { + projectPathItemStat, err := os.Lstat(projectPathItem.String()) + if err != nil { + panic(err) + } + + if projectPathItemStat.Mode()&os.ModeSymlink != 0 { + symlinkPaths = append(symlinkPaths, projectPathItem.String()) + } + } + + if len(symlinkPaths) > 0 { + return checkresult.Fail, strings.Join(symlinkPaths, ", ") + } + + return checkresult.Pass, "" +} + // spellCheckLibraryPropertiesFieldValue returns the value of the provided library.properties field with commonly misspelled words corrected. func spellCheckLibraryPropertiesFieldValue(fieldName string) (result checkresult.Type, output string) { if checkdata.LibraryPropertiesLoadError() != nil { diff --git a/check/checkfunctions/library_test.go b/check/checkfunctions/library_test.go index 8d59cc9f2..d9ba882f3 100644 --- a/check/checkfunctions/library_test.go +++ b/check/checkfunctions/library_test.go @@ -26,6 +26,7 @@ import ( "github.com/arduino/arduino-check/project/projecttype" "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var testDataPath *paths.Path @@ -200,3 +201,27 @@ func TestLibraryHasSubmodule(t *testing.T) { checkCheckFunction(LibraryHasSubmodule, testTables, t) } + +func TestLibraryContainsSymlinks(t *testing.T) { + testLibrary := "Recursive" + symlinkPath := testDataPath.Join(testLibrary, "test-symlink") + // It's probably most friendly to developers using Windows to create the symlink needed for the test on demand. + err := os.Symlink(testDataPath.Join(testLibrary, "library.properties").String(), symlinkPath.String()) + require.Nil(t, err, "This test must be run as administrator on Windows to have symlink creation privilege.") + defer symlinkPath.RemoveAll() // clean up + + testTables := []checkFunctionTestTable{ + {"Has symlink", testLibrary, checkresult.Fail, ""}, + } + + checkCheckFunction(LibraryContainsSymlinks, testTables, t) + + err = symlinkPath.RemoveAll() + require.Nil(t, err) + + testTables = []checkFunctionTestTable{ + {"No symlink", testLibrary, checkresult.Pass, ""}, + } + + checkCheckFunction(LibraryContainsSymlinks, testTables, t) +}