diff --git a/README.md b/README.md new file mode 100644 index 000000000..07fa063f1 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# arduino-check + +`arduino-check` automatically checks for common problems in your [Arduino](https://www.arduino.cc/) projects: + +- Sketches +- Libraries diff --git a/arduino-library-properties-schema.json b/arduino-library-properties-schema.json new file mode 100644 index 000000000..ffda4d7b6 --- /dev/null +++ b/arduino-library-properties-schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://github.com/arduino/arduino-check/arduino-library-properties-schema.json", + "title": "Arduino library.properties JSON schema", + "description": "library.properties is the metadata file for Arduino libraries. See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata", + "$comment": "For information on the Arduino library.properties format, see https://godoc.org/github.com/arduino/go-properties-orderedmap", + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 63, + "pattern": "^(([a-zA-Z][a-zA-Z0-9 _\\.\\-]*)|([0-9][a-zA-Z0-9 _\\.\\-]*[a-zA-Z][a-zA-Z0-9 _\\.\\-]*))$" + }, + "version": { + "type": "string", + "$comment": "https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string adjusted to also allow MAJOR.MINOR and with unused non-capturing group syntax removed", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(-((0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*))?$" + }, + "author": { + "type": "string", + "minLength": 1 + }, + "maintainer": { + "type": "string", + "minLength": 1 + }, + "sentence": { + "type": "string", + "minLength": 1 + }, + "paragraph": { + "type": "string" + }, + "category": { + "type": "string", + "enum": [ + "Display", + "Communication", + "Signal Input/Output", + "Sensors", + "Device Control", + "Timing", + "Data Storage", + "Data Processing", + "Other" + ] + }, + "url": { + "type": "string", + "format": "uri" + }, + "architectures": { + "type": "string", + "minLength": 1 + }, + "depends": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z\\d _\\.\\-,]*$" + }, + "dot_a_linkage": { + "type": "string", + "enum": ["true", "false"] + }, + "includes": { + "type": "string", + "minLength": 1 + }, + "precompiled": { + "type": "string", + "enum": ["true", "full", "false"] + }, + "ldflags": { + "type": "string" + } + }, + "propertyNames": { + "not": { + "$comment": "Misspelled optional property names", + "pattern": "^((depend)|(D((epends?)|(EPENDS?)))|(dot_a_linkages)|(dot-?a-?linkages?)|(D(((ot)|(OT))[_-]?((a)|(A))[_-]?((linkages?)|(LINKAGES?))))|(include)|(I((ncludes?)|(NCLUDES?)))|(precompile)|(pre[-_]compiled?)|(P((re[-_]?compiled?)|(RE[-_]?COMPILED?)))|(ldflag)|(ld[-_]flags?)|(L((d[-_]?flags?)|(D[-_]?FLAGS?))))$" + } + }, + "required": ["name", "version", "author", "maintainer", "sentence", "paragraph", "category", "url", "architectures"] +} diff --git a/check/check.go b/check/check.go new file mode 100644 index 000000000..5bf557817 --- /dev/null +++ b/check/check.go @@ -0,0 +1,91 @@ +// Package check runs checks on a project. +package check + +import ( + "fmt" + "os" + + "github.com/arduino/arduino-check/check/checkconfigurations" + "github.com/arduino/arduino-check/check/checkdata" + "github.com/arduino/arduino-check/configuration" + "github.com/arduino/arduino-check/configuration/checkmode" + "github.com/arduino/arduino-check/project" + "github.com/arduino/arduino-check/result" + "github.com/arduino/arduino-check/result/feedback" + "github.com/arduino/arduino-check/result/outputformat" + "github.com/sirupsen/logrus" +) + +// RunChecks runs all checks for the given project and outputs the results. +func RunChecks(project project.Type) { + fmt.Printf("Checking %s in %s\n", project.ProjectType, project.Path) + + checkdata.Initialize(project) + + for _, checkConfiguration := range checkconfigurations.Configurations() { + runCheck, err := shouldRun(checkConfiguration, project) + if err != nil { + feedback.Errorf("Error while determining whether to run check: %v", err) + os.Exit(1) + } + + if !runCheck { + logrus.Infof("Skipping check: %s\n", checkConfiguration.ID) + continue + } + + // Output will be printed after all checks are finished when configured for "json" output format + if configuration.OutputFormat() == outputformat.Text { + fmt.Printf("Running check %s: ", checkConfiguration.ID) + } + checkResult, checkOutput := checkConfiguration.CheckFunction() + reportText := result.Results.Record(project, checkConfiguration, checkResult, checkOutput) + if configuration.OutputFormat() == outputformat.Text { + fmt.Print(reportText) + } + } + + // Checks are finished for this project, so summarize its check results in the report. + result.Results.AddProjectSummary(project) + + if configuration.OutputFormat() == outputformat.Text { + // Print the project check results summary. + fmt.Print(result.Results.ProjectSummaryText(project)) + } +} + +// shouldRun returns whether a given check should be run for the given project under the current tool configuration. +func shouldRun(checkConfiguration checkconfigurations.Type, currentProject project.Type) (bool, error) { + configurationCheckModes := configuration.CheckModes(currentProject.SuperprojectType) + + if checkConfiguration.ProjectType != currentProject.ProjectType { + return false, nil + } + + for _, disableMode := range checkConfiguration.DisableModes { + if configurationCheckModes[disableMode] { + return false, nil + } + } + + for _, enableMode := range checkConfiguration.EnableModes { + if configurationCheckModes[enableMode] { + return true, nil + } + } + + // Use default + for _, disableMode := range checkConfiguration.DisableModes { + if disableMode == checkmode.Default { + return false, nil + } + } + + for _, enableMode := range checkConfiguration.EnableModes { + if enableMode == checkmode.Default { + return true, nil + } + } + + return false, fmt.Errorf("Check %s is incorrectly configured", checkConfiguration.ID) +} diff --git a/check/checkconfigurations/checkconfigurations.go b/check/checkconfigurations/checkconfigurations.go new file mode 100644 index 000000000..cded58f71 --- /dev/null +++ b/check/checkconfigurations/checkconfigurations.go @@ -0,0 +1,119 @@ +/* +Package checkconfigurations defines the configuration of each check: +- metadata +- output template +- under which conditions it's enabled +- the level of a failure +- which function implements it +*/ +package checkconfigurations + +import ( + "github.com/arduino/arduino-check/check/checkfunctions" + "github.com/arduino/arduino-check/configuration/checkmode" + "github.com/arduino/arduino-check/project/projecttype" +) + +// Type is the type for check configurations. +type Type struct { + ProjectType projecttype.Type // The project type the check applies to. + // The following fields provide arbitrary text for the tool output associated with each check: + Category string + Subcategory string + ID string // Unique check identifier: + Brief string // Short description of the check. + Description string // Supplemental information about the check. + MessageTemplate string // The warning/error message template displayed when the check fails. Will be filled by check function output. + // The following fields define under which tool configuration modes the check will run: + DisableModes []checkmode.Type // Check is disabled when tool is in any of these modes. + EnableModes []checkmode.Type // Check is only enabled when tool is in one of these modes. + // The following fields define the check level in each configuration mode: + InfoModes []checkmode.Type // Failure of the check only results in an informational message. + WarningModes []checkmode.Type // Failure of the check is considered a warning. + ErrorModes []checkmode.Type // Failure of the check is considered an error. + CheckFunction checkfunctions.Type // The function that implements the check. +} + +// Configurations returns the slice of check configurations. +func Configurations() []Type { + return configurations +} + +// configurations is an array of structs that define the configuration of each check. +var configurations = []Type{ + { + ProjectType: projecttype.Library, + Category: "library.properties", + Subcategory: "general", + ID: "LP001", + Brief: "invalid format", + Description: "", + MessageTemplate: "library.properties has an invalid format: {{.}}", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: nil, + ErrorModes: []checkmode.Type{checkmode.All}, + CheckFunction: checkfunctions.LibraryPropertiesFormat, + }, + { + ProjectType: projecttype.Library, + Category: "library.properties", + Subcategory: "name field", + ID: "LP002", + Brief: "missing name field", + Description: "", + MessageTemplate: "missing name field in library.properties", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: nil, + ErrorModes: []checkmode.Type{checkmode.All}, + CheckFunction: checkfunctions.LibraryPropertiesNameFieldMissing, + }, + { + ProjectType: projecttype.Library, + Category: "library.properties", + Subcategory: "name field", + ID: "LP003", + Brief: "disallowed characters", + Description: "", + MessageTemplate: "disallowed characters in library.properties name field. See: https://arduino.github.io/arduino-cli/latest/library-specification/#libraryproperties-file-format", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: nil, + ErrorModes: []checkmode.Type{checkmode.All}, + CheckFunction: checkfunctions.LibraryPropertiesNameFieldDisallowedCharacters, + }, + { + ProjectType: projecttype.Library, + Category: "library.properties", + Subcategory: "version field", + ID: "LP004", + Brief: "missing version field", + Description: "", + MessageTemplate: "missing version field in library.properties", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: nil, + ErrorModes: []checkmode.Type{checkmode.All}, + CheckFunction: checkfunctions.LibraryPropertiesVersionFieldMissing, + }, + { + ProjectType: projecttype.Sketch, + Category: "structure", + Subcategory: "", + ID: "SS001", + Brief: ".pde extension", + Description: "The .pde extension is used by both Processing sketches and Arduino sketches. Processing sketches should either be in the \"data\" subfolder of the sketch or in the \"extras\" folder of the library. Arduino sketches should use the modern .ino extension", + MessageTemplate: "{{.}} uses deprecated .pde file extension. Use .ino for Arduino sketches", + DisableModes: nil, + EnableModes: []checkmode.Type{checkmode.All}, + InfoModes: nil, + WarningModes: []checkmode.Type{checkmode.Permissive}, + ErrorModes: []checkmode.Type{checkmode.Default}, + CheckFunction: checkfunctions.PdeSketchExtension, + }, +} diff --git a/check/checkdata/checkdata.go b/check/checkdata/checkdata.go new file mode 100644 index 000000000..aaee70a9a --- /dev/null +++ b/check/checkdata/checkdata.go @@ -0,0 +1,38 @@ +/* +Package checkdata handles the collection of data specific to a project before running the checks on it. +This is for data required by multiple checks. +*/ +package checkdata + +import ( + "github.com/arduino/arduino-check/project" + "github.com/arduino/arduino-check/project/projecttype" + "github.com/arduino/go-paths-helper" +) + +// Initialize gathers the check data for the specified project. +func Initialize(project project.Type) { + projectType = project.ProjectType + projectPath = project.Path + switch project.ProjectType { + case projecttype.Sketch: + case projecttype.Library: + InitializeForLibrary(project) + case projecttype.Platform: + case projecttype.PackageIndex: + } +} + +var projectType projecttype.Type + +// ProjectType returns the type of the project being checked. +func ProjectType() projecttype.Type { + return projectType +} + +var projectPath *paths.Path + +// ProjectPath returns the path to the project being checked. +func ProjectPath() *paths.Path { + return projectPath +} diff --git a/check/checkdata/library.go b/check/checkdata/library.go new file mode 100644 index 000000000..361b847e3 --- /dev/null +++ b/check/checkdata/library.go @@ -0,0 +1,41 @@ +package checkdata + +import ( + "github.com/arduino/arduino-check/project" + "github.com/arduino/arduino-check/project/library/libraryproperties" + "github.com/arduino/go-properties-orderedmap" + "github.com/xeipuuv/gojsonschema" +) + +// Initialize gathers the library check data for the specified project. +func InitializeForLibrary(project project.Type) { + libraryProperties, libraryPropertiesLoadError = libraryproperties.Properties(project.Path) + if libraryPropertiesLoadError != nil { + // TODO: can I even do this? + libraryPropertiesSchemaValidationResult = nil + } else { + libraryPropertiesSchemaValidationResult = libraryproperties.Validate(libraryProperties) + } +} + +var libraryPropertiesLoadError error + +// LibraryPropertiesLoadError returns the error output from loading the library.properties metadata file. +func LibraryPropertiesLoadError() error { + return libraryPropertiesLoadError +} + +var libraryProperties *properties.Map + +// LibraryProperties returns the data from the library.properties metadata file. +func LibraryProperties() *properties.Map { + return libraryProperties +} + +var libraryPropertiesSchemaValidationResult *gojsonschema.Result + +// LibraryPropertiesSchemaValidationResult returns the result of validating library.properties against the JSON schema. +// See: https://github.com/xeipuuv/gojsonschema +func LibraryPropertiesSchemaValidationResult() *gojsonschema.Result { + return libraryPropertiesSchemaValidationResult +} diff --git a/check/checkfunctions/checkfunctions.go b/check/checkfunctions/checkfunctions.go new file mode 100644 index 000000000..42f4e2cd7 --- /dev/null +++ b/check/checkfunctions/checkfunctions.go @@ -0,0 +1,10 @@ +// Package checkfunctions contains the functions that implement each check. +package checkfunctions + +import ( + "github.com/arduino/arduino-check/check/checkresult" +) + +// Type is the function signature for the check functions. +// The `output` result is the contextual information that will be inserted into the check's message template. +type Type func() (result checkresult.Type, output string) diff --git a/check/checkfunctions/library.go b/check/checkfunctions/library.go new file mode 100644 index 000000000..f3419b382 --- /dev/null +++ b/check/checkfunctions/library.go @@ -0,0 +1,54 @@ +package checkfunctions + +// The check functions for libraries. + +import ( + "github.com/arduino/arduino-check/check/checkdata" + "github.com/arduino/arduino-check/check/checkresult" + "github.com/arduino/arduino-check/project/library/libraryproperties" +) + +// LibraryPropertiesFormat checks for invalid library.properties format. +func LibraryPropertiesFormat() (result checkresult.Type, output string) { + if checkdata.LibraryPropertiesLoadError() != nil { + return checkresult.Fail, checkdata.LibraryPropertiesLoadError().Error() + } + return checkresult.Pass, "" +} + +// LibraryPropertiesNameFieldMissing checks for missing library.properties "name" field. +func LibraryPropertiesNameFieldMissing() (result checkresult.Type, output string) { + if checkdata.LibraryPropertiesLoadError() != nil { + return checkresult.NotRun, "" + } + + if libraryproperties.FieldMissing("name", checkdata.LibraryPropertiesSchemaValidationResult()) { + return checkresult.Fail, "" + } + return checkresult.Pass, "" +} + +// LibraryPropertiesNameFieldDisallowedCharacters checks for disallowed characters in the library.properties "name" field. +func LibraryPropertiesNameFieldDisallowedCharacters() (result checkresult.Type, output string) { + if checkdata.LibraryPropertiesLoadError() != nil { + return checkresult.NotRun, "" + } + + if libraryproperties.FieldPatternMismatch("name", checkdata.LibraryPropertiesSchemaValidationResult()) { + return checkresult.Fail, "" + } + + return checkresult.Pass, "" +} + +// LibraryPropertiesVersionFieldMissing checks for missing library.properties "version" field. +func LibraryPropertiesVersionFieldMissing() (result checkresult.Type, output string) { + if checkdata.LibraryPropertiesLoadError() != nil { + return checkresult.NotRun, "" + } + + if libraryproperties.FieldMissing("version", checkdata.LibraryPropertiesSchemaValidationResult()) { + return checkresult.Fail, "" + } + return checkresult.Pass, "" +} diff --git a/check/checkfunctions/sketch.go b/check/checkfunctions/sketch.go new file mode 100644 index 000000000..741becc43 --- /dev/null +++ b/check/checkfunctions/sketch.go @@ -0,0 +1,28 @@ +package checkfunctions + +// The check functions for sketches. + +import ( + "strings" + + "github.com/arduino/arduino-check/check/checkdata" + "github.com/arduino/arduino-check/check/checkresult" +) + +// PdeSketchExtension checks for use of deprecated .pde sketch file extensions. +func PdeSketchExtension() (result checkresult.Type, output string) { + directoryListing, _ := checkdata.ProjectPath().ReadDir() + directoryListing.FilterOutDirs() + pdeSketches := []string{} + for _, filePath := range directoryListing { + if filePath.Ext() == ".pde" { + pdeSketches = append(pdeSketches, filePath.Base()) + } + } + + if len(pdeSketches) > 0 { + return checkresult.Fail, strings.Join(pdeSketches, ", ") + } + + return checkresult.Pass, "" +} diff --git a/check/checklevel/checklevel.go b/check/checklevel/checklevel.go new file mode 100644 index 000000000..8510d13fa --- /dev/null +++ b/check/checklevel/checklevel.go @@ -0,0 +1,65 @@ +// Package checklevel defines the level assigned to a check failure. +package checklevel + +import ( + "fmt" + + "github.com/arduino/arduino-check/check/checkconfigurations" + "github.com/arduino/arduino-check/configuration" + "github.com/arduino/arduino-check/configuration/checkmode" +) + +// Type is the type for the check levels. +//go:generate stringer -type=Type -linecomment +type Type int + +// The line comments set the string for each level. +const ( + Info Type = iota // info + Warning // warning + Error // error + Notice // notice +) + +// CheckLevel determines the check level assigned to failure of the given check under the current tool configuration. +func CheckLevel(checkConfiguration checkconfigurations.Type) (Type, error) { + configurationCheckModes := configuration.CheckModes(checkConfiguration.ProjectType) + for _, errorMode := range checkConfiguration.ErrorModes { + if configurationCheckModes[errorMode] { + return Error, nil + } + } + + for _, warningMode := range checkConfiguration.WarningModes { + if configurationCheckModes[warningMode] { + return Warning, nil + } + } + + for _, infoMode := range checkConfiguration.InfoModes { + if configurationCheckModes[infoMode] { + return Info, nil + } + } + + // Use default level + for _, errorMode := range checkConfiguration.ErrorModes { + if errorMode == checkmode.Default { + return Error, nil + } + } + + for _, warningMode := range checkConfiguration.WarningModes { + if warningMode == checkmode.Default { + return Warning, nil + } + } + + for _, infoMode := range checkConfiguration.InfoModes { + if infoMode == checkmode.Default { + return Info, nil + } + } + + return Notice, fmt.Errorf("Check %s is incorrectly configured", checkConfiguration.ID) +} diff --git a/check/checklevel/type_string.go b/check/checklevel/type_string.go new file mode 100644 index 000000000..89bbacf56 --- /dev/null +++ b/check/checklevel/type_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package checklevel + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Info-0] + _ = x[Warning-1] + _ = x[Error-2] + _ = x[Notice-3] +} + +const _Type_name = "infowarningerrornotice" + +var _Type_index = [...]uint8{0, 4, 11, 16, 22} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/check/checkresult/checkresult.go b/check/checkresult/checkresult.go new file mode 100644 index 000000000..e63f80759 --- /dev/null +++ b/check/checkresult/checkresult.go @@ -0,0 +1,14 @@ +// Package checkresult defines the possible result values returned by a check. +package checkresult + +//go:generate stringer -type=Type -linecomment +type Type int + +const ( + Pass Type = iota // pass + Fail // fail + // The check is configured to be skipped in the current tool configuration mode + Skip // skipped + // An unrelated error prevented the check from running + NotRun // unable to run +) diff --git a/check/checkresult/type_string.go b/check/checkresult/type_string.go new file mode 100644 index 000000000..427645627 --- /dev/null +++ b/check/checkresult/type_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package checkresult + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Pass-0] + _ = x[Fail-1] + _ = x[Skip-2] + _ = x[NotRun-3] +} + +const _Type_name = "passfailskippedunable to run" + +var _Type_index = [...]uint8{0, 4, 8, 15, 28} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/configuration/checkmode/checkmode.go b/configuration/checkmode/checkmode.go new file mode 100644 index 000000000..120384b20 --- /dev/null +++ b/configuration/checkmode/checkmode.go @@ -0,0 +1,40 @@ +// Package checkmode defines the tool configuration options that affect checks. +package checkmode + +import ( + "github.com/arduino/arduino-check/project/projecttype" + "github.com/sirupsen/logrus" +) + +// Type is the type for check modes. +type Type int + +//go:generate stringer -type=Type -linecomment +const ( + Permissive Type = iota // --permissive + LibraryManagerSubmission // --library-manager=submit + LibraryManagerIndexed // --library-manager=update + Official // ARDUINO_CHECK_OFFICIAL + All // always + Default // default +) + +// Modes merges the default check mode values for the given superproject type with any user-specified check mode settings. +func Modes(defaultCheckModes map[projecttype.Type]map[Type]bool, customCheckModes map[Type]bool, superprojectType projecttype.Type) map[Type]bool { + checkModes := make(map[Type]bool) + + for key, defaultValue := range defaultCheckModes[superprojectType] { + customCheckModeValue, customCheckModeIsConfigured := customCheckModes[key] + if customCheckModeIsConfigured { + checkModes[key] = customCheckModeValue + } else { + checkModes[key] = defaultValue + } + logrus.Tracef("Check mode option %s set to %t\n", key, checkModes[key]) + } + + // This mode is always enabled + checkModes[All] = true + + return checkModes +} diff --git a/configuration/checkmode/type_string.go b/configuration/checkmode/type_string.go new file mode 100644 index 000000000..74fc6c30d --- /dev/null +++ b/configuration/checkmode/type_string.go @@ -0,0 +1,28 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package checkmode + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Permissive-0] + _ = x[LibraryManagerSubmission-1] + _ = x[LibraryManagerIndexed-2] + _ = x[Official-3] + _ = x[All-4] + _ = x[Default-5] +} + +const _Type_name = "--permissive--library-manager=submit--library-manager=updateARDUINO_CHECK_OFFICIALalwaysdefault" + +var _Type_index = [...]uint8{0, 12, 36, 60, 82, 88, 95} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/configuration/configuration.go b/configuration/configuration.go new file mode 100644 index 000000000..ba66592fe --- /dev/null +++ b/configuration/configuration.go @@ -0,0 +1,81 @@ +// Package configuration handles the configuration of the arduino-check tool. +package configuration + +import ( + "github.com/arduino/arduino-check/configuration/checkmode" + "github.com/arduino/arduino-check/project/projecttype" + "github.com/arduino/arduino-check/result/outputformat" + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" +) + +// TODO: will it be possible to use init() instead? +// Initialize sets up the tool configuration according to defaults and user-specified options. +func Initialize() { + setDefaults() + // TODO configuration according to command line input + // TODO validate target path value, exit if not found + // TODO support multiple paths + // TODO validate output format input + + targetPath = paths.New("e:/electronics/arduino/libraries/arduino-check-test-library") + + // customCheckModes[checkmode.Permissive] = false + // customCheckModes[checkmode.LibraryManagerSubmission] = false + // customCheckModes[checkmode.LibraryManagerIndexed] = false + // customCheckModes[checkmode.Official] = false + // superprojectType = projecttype.All + + outputFormat = outputformat.JSON + //reportFilePath = paths.New("report.json") + + logrus.SetLevel(logrus.PanicLevel) + + logrus.WithFields(logrus.Fields{ + "superproject type filter": SuperprojectTypeFilter(), + "recursive": Recursive(), + "projects path": TargetPath(), + }).Debug("Configuration initialized") +} + +var customCheckModes = make(map[checkmode.Type]bool) + +// CheckModes returns the check modes configuration for the given project type. +func CheckModes(superprojectType projecttype.Type) map[checkmode.Type]bool { + return checkmode.Modes(defaultCheckModes, customCheckModes, superprojectType) +} + +var superprojectTypeFilter projecttype.Type + +// SuperprojectType returns the superproject type filter configuration. +func SuperprojectTypeFilter() projecttype.Type { + return superprojectTypeFilter +} + +var recursive bool + +// Recursive returns the recursive project search configuration value. +func Recursive() bool { + return recursive +} + +var outputFormat outputformat.Type + +// OutputFormat returns the tool output format configuration value. +func OutputFormat() outputformat.Type { + return outputFormat +} + +var reportFilePath *paths.Path + +// ReportFilePath returns the path to save the report file at. +func ReportFilePath() *paths.Path { + return reportFilePath +} + +var targetPath *paths.Path + +// TargetPath returns the projects search path. +func TargetPath() *paths.Path { + return targetPath +} diff --git a/configuration/defaults.go b/configuration/defaults.go new file mode 100644 index 000000000..b17dc3f37 --- /dev/null +++ b/configuration/defaults.go @@ -0,0 +1,45 @@ +package configuration + +// The default configuration settings. + +import ( + "github.com/arduino/arduino-check/configuration/checkmode" + "github.com/arduino/arduino-check/project/projecttype" + "github.com/arduino/arduino-check/result/outputformat" +) + +func setDefaults() { + superprojectTypeFilter = projecttype.All + recursive = true + outputFormat = outputformat.Text + // TODO: targetPath defaults to current path +} + +// Default check modes for each superproject type +// Subprojects use the same check modes as the superproject +var defaultCheckModes = map[projecttype.Type]map[checkmode.Type]bool{ + projecttype.Sketch: { + checkmode.Permissive: false, + checkmode.LibraryManagerSubmission: false, + checkmode.LibraryManagerIndexed: false, + checkmode.Official: false, + }, + projecttype.Library: { + checkmode.Permissive: false, + checkmode.LibraryManagerSubmission: true, + checkmode.LibraryManagerIndexed: false, + checkmode.Official: false, + }, + projecttype.Platform: { + checkmode.Permissive: false, + checkmode.LibraryManagerSubmission: false, + checkmode.LibraryManagerIndexed: false, + checkmode.Official: false, + }, + projecttype.PackageIndex: { + checkmode.Permissive: false, + checkmode.LibraryManagerSubmission: false, + checkmode.LibraryManagerIndexed: false, + checkmode.Official: false, + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..6d7d5cfd8 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/arduino/arduino-check + +go 1.14 + +require ( + github.com/arduino/arduino-cli v0.0.0-20200922073731-53e3230c4f71 + github.com/arduino/go-paths-helper v1.3.2 + github.com/arduino/go-properties-orderedmap v1.4.0 + github.com/sirupsen/logrus v1.6.0 + github.com/xeipuuv/gojsonschema v1.2.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..036d3d4ee --- /dev/null +++ b/go.sum @@ -0,0 +1,280 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/arduino/arduino-cli v0.0.0-20200922073731-53e3230c4f71 h1:wqYJLwv4C+lpGbtqNgGVy5MNKQx8ET+/kDukD2nE9hs= +github.com/arduino/arduino-cli v0.0.0-20200922073731-53e3230c4f71/go.mod h1:FoP2RfB4sX+XvDiLKysORud9lv8HtgYq14sNawOnx7o= +github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8= +github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= +github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= +github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= +github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cmaglie/go.rice v1.0.3/go.mod h1:AF3bOWkvdOpp8/S3UL8qbQ4N7DiISIbJtj54GWFPAsc= +github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI= +github.com/codeclysm/cc v1.2.2/go.mod h1:XtW4ArCNgQwFphcRGG9+sPX5WM1J6/u0gMy5ZdV3obA= +github.com/codeclysm/extract/v3 v3.0.1/go.mod h1:NKsw+hqua9H+Rlwy/w/3Qgt9jDonYEgB6wJu+25eOKw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= +github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/filetype v1.0.6/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= +github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imjasonmiller/godice v0.1.2/go.mod h1:8cTkdnVI+NglU2d6sv+ilYcNaJ5VSTBwvMbFULJd/QQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= +github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/retry v0.0.0-20160928201858-1998d01ba1c3/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= +github.com/juju/utils v0.0.0-20180808125547-9dfc6dbfb02b/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2spuF/i/Qlsvz1Us= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/marcinbor85/gohex v0.0.0-20200531163658-baab2527a9a2/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/genetlink v0.0.0-20190313224034-60417448a851/go.mod h1:EsbsAEUEs15qC1cosAwxgCWV0Qhd8TmkxnA9Kw1Vhl4= +github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/taskstats v0.0.0-20190313225729-7cbba52ee072/go.mod h1:sGdS7A6CAETR53zkdjGkgoFlh1vSm7MtX+i8XfEsTMA= +github.com/miekg/dns v1.0.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583/go.mod h1:sFPiU/UgDcsQVu3vkqpZLCXWFwUoQRpHGu9ATihPAl0= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +github.com/segmentio/stats/v4 v4.5.3/go.mod h1:LsaahUJR7iiSs8mnkvQvdQ/RLHAS5adGLxuntg0ydGo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.1-0.20200710201246-675ae5f5a98c/go.mod h1:aeNIJzz/GSSVlS+gpCpQWZ83BKbsoW57mr90+YthtkQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk= +go.bug.st/downloader/v2 v2.0.1/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII= +go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18/go.mod h1:Cx1VqMtEhE9pIkEyUj3LVVVPkv89dgW8aCKrRPDR/uE= +go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q= +go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go new file mode 100644 index 000000000..53101788e --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "os" + + "github.com/arduino/arduino-check/check" + "github.com/arduino/arduino-check/configuration" + "github.com/arduino/arduino-check/project" + "github.com/arduino/arduino-check/result" + "github.com/arduino/arduino-check/result/feedback" + "github.com/arduino/arduino-check/result/outputformat" +) + +func main() { + configuration.Initialize() + // Must be called after configuration.Initialize() + result.Results.Initialize() + + projects, err := project.FindProjects() + if err != nil { + feedback.Errorf("Error while finding projects: %v", err) + os.Exit(1) + } + + for _, project := range projects { + check.RunChecks(project) + } + + // All projects have been checked, so summarize their check results in the report. + result.Results.AddSummary() + + if configuration.OutputFormat() == outputformat.Text { + if len(projects) > 1 { + // There are multiple projects, print the summary of check results for all projects. + fmt.Print(result.Results.SummaryText()) + } + } else { + // Print the complete JSON formatted report. + fmt.Println(result.Results.JSONReport()) + } + + if configuration.ReportFilePath() != nil { + // Write report file. + result.Results.WriteReport() + } + + if !result.Results.Passed() { + os.Exit(1) + } +} diff --git a/project/library/library.go b/project/library/library.go new file mode 100644 index 000000000..e6d4bf75a --- /dev/null +++ b/project/library/library.go @@ -0,0 +1,57 @@ +// Package library provides functions specific to checking Arduino libraries. +package library + +import ( + "github.com/arduino/go-paths-helper" +) + +var empty struct{} + +// Reference: https://github.com/arduino/arduino-cli/blob/0.13.0/arduino/libraries/libraries.go#L167 +var headerFileValidExtensions = map[string]struct{}{ + ".h": empty, + ".hpp": empty, + ".hh": empty, +} + +// HasHeaderFileValidExtension returns whether the file at the given path has a valid library header file extension. +func HasHeaderFileValidExtension(filePath *paths.Path) bool { + _, hasHeaderFileValidExtension := headerFileValidExtensions[filePath.Ext()] + return hasHeaderFileValidExtension +} + +// See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata +var metadataFilenames = map[string]struct{}{ + "library.properties": empty, +} + +// IsMetadataFile returns whether the file at the given path is an Arduino library metadata file. +func IsMetadataFile(filePath *paths.Path) bool { + _, isMetadataFile := metadataFilenames[filePath.Base()] + if isMetadataFile { + return true + } + return false +} + +// See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-examples +var examplesFolderValidNames = map[string]struct{}{ + "examples": empty, +} + +// Only "examples" is specification-compliant, but apparently "example" is also supported +// See: https://github.com/arduino/arduino-cli/blob/0.13.0/arduino/libraries/loader.go#L153 +var examplesFolderSupportedNames = map[string]struct{}{ + "examples": empty, + "example": empty, +} + +// ExamplesFolderNames returns a slice of supported examples folder names +func ExamplesFolderSupportedNames() []string { + folderNames := make([]string, 0, len(examplesFolderSupportedNames)) + for folderName := range examplesFolderSupportedNames { + folderNames = append(folderNames, folderName) + } + + return folderNames +} diff --git a/project/library/libraryproperties/libraryproperties.go b/project/library/libraryproperties/libraryproperties.go new file mode 100644 index 000000000..a64b2bcd0 --- /dev/null +++ b/project/library/libraryproperties/libraryproperties.go @@ -0,0 +1,72 @@ +// Package libraryproperties provides functions for working with the library.properties Arduino library metadata file. +package libraryproperties + +import ( + "net/url" + "os" + "path/filepath" + + "github.com/arduino/go-paths-helper" + "github.com/arduino/go-properties-orderedmap" + "github.com/xeipuuv/gojsonschema" +) + +// Properties parses the library.properties from the given path and returns the data. +func Properties(libraryPath *paths.Path) (*properties.Map, error) { + libraryProperties, err := properties.Load(libraryPath.Join("library.properties").String()) + if err != nil { + return nil, err + } + return libraryProperties, nil +} + +// Validate validates library.properties data against the JSON schema. +func Validate(libraryProperties *properties.Map) *gojsonschema.Result { + workingPath, _ := os.Getwd() + schemaPath := paths.New(workingPath).Join("arduino-library-properties-schema.json") + uriFriendlySchemaPath := filepath.ToSlash(schemaPath.String()) + schemaPathURI := url.URL{ + Scheme: "file", + Path: uriFriendlySchemaPath, + } + schemaLoader := gojsonschema.NewReferenceLoader(schemaPathURI.String()) + + documentLoader := gojsonschema.NewGoLoader(libraryProperties) + + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + panic(err.Error()) + } + + return result +} + +// FieldMissing returns whether the given required field is missing from library.properties. +func FieldMissing(fieldName string, validationResult *gojsonschema.Result) bool { + return ValidationErrorMatch("required", "(root)", fieldName+" is required", validationResult) +} + +// FieldPatternMismatch returns whether the given field did not match the regular expression defined in the JSON schema. +func FieldPatternMismatch(fieldName string, validationResult *gojsonschema.Result) bool { + return ValidationErrorMatch("pattern", fieldName, "", validationResult) +} + +// ValidationErrorMatch returns whether the given query matches against the JSON schema validation error. +// See: https://github.com/xeipuuv/gojsonschema#working-with-errors +func ValidationErrorMatch(typeQuery string, fieldQuery string, descriptionQuery string, validationResult *gojsonschema.Result) bool { + if validationResult.Valid() { + // No error, so nothing to match + return false + } + for _, validationError := range validationResult.Errors() { + if typeQuery == "" || typeQuery == validationError.Type() { + if fieldQuery == "" || fieldQuery == validationError.Field() { + if descriptionQuery == "" || descriptionQuery == validationError.Description() { + return true + } + } + } + } + + return false +} diff --git a/project/packageindex/packageindex.go b/project/packageindex/packageindex.go new file mode 100644 index 000000000..d2005b90b --- /dev/null +++ b/project/packageindex/packageindex.go @@ -0,0 +1,38 @@ +/* +Package packageindex provides functions specific to checking the package index files of the Arduino Boards Manager. +See: https://arduino.github.io/arduino-cli/latest/package_index_json-specification +*/ +package packageindex + +import ( + "regexp" + + "github.com/arduino/go-paths-helper" +) + +var empty struct{} + +// Reference: https://arduino.github.io/arduino-cli/latest/package_index_json-specification/#naming-of-the-json-index-file +var validExtensions = map[string]struct{}{ + ".json": empty, +} + +// HasValidExtension returns whether the file at the given path has a valid package index extension. +func HasValidExtension(filePath *paths.Path) bool { + _, hasValidExtension := validExtensions[filePath.Ext()] + return hasValidExtension +} + +// Regular expressions for official and non-official package index filenames +// See: https://arduino.github.io/arduino-cli/latest/package_index_json-specification/#naming-of-the-json-index-file +var validFilenameRegex = map[bool]*regexp.Regexp{ + true: regexp.MustCompile(`^package_(.+_)*index.json$`), + false: regexp.MustCompile(`^package_(.+_)+index.json$`), +} + +// HasValidFilename returns whether the file at the given path has a valid package index filename. +func HasValidFilename(filePath *paths.Path, officialCheckMode bool) bool { + regex := validFilenameRegex[officialCheckMode] + filename := filePath.Base() + return regex.MatchString(filename) +} diff --git a/project/platform/platform.go b/project/platform/platform.go new file mode 100644 index 000000000..a6534ad9f --- /dev/null +++ b/project/platform/platform.go @@ -0,0 +1,50 @@ +/* +Package packageindex provides functions specific to checking Arduino boards platforms. +See: https://arduino.github.io/arduino-cli/latest/platform-specification/ +*/ +package platform + +import ( + "github.com/arduino/go-paths-helper" +) + +var empty struct{} + +var configurationFilenames = map[string]struct{}{ + "boards.txt": empty, + "boards.local.txt": empty, + "platform.txt": empty, + "platform.local.txt": empty, + "programmers.txt": empty, +} + +// IsConfigurationFile returns whether the file at the given path has a boards platform configuration file filename. +func IsConfigurationFile(filePath *paths.Path) bool { + _, isConfigurationFile := configurationFilenames[filePath.Base()] + return isConfigurationFile +} + +var requiredConfigurationFilenames = map[string]struct{}{ + // Arduino platforms must always contain a boards.txt + "boards.txt": empty, +} + +// IsRequiredConfigurationFile returns whether the file at the given path has the filename of a required boards platform configuration file. +func IsRequiredConfigurationFile(filePath *paths.Path) bool { + _, isRequiredConfigurationFile := requiredConfigurationFilenames[filePath.Base()] + return isRequiredConfigurationFile +} + +// See: https://arduino.github.io/arduino-cli/latest/platform-specification/#platform-bundled-libraries +var bundledLibrariesFolderNames = map[string]struct{}{ + "libraries": empty, +} + +func BundledLibrariesFolderNames() []string { + folderNames := make([]string, 0, len(bundledLibrariesFolderNames)) + for folderName := range bundledLibrariesFolderNames { + folderNames = append(folderNames, folderName) + } + + return folderNames +} diff --git a/project/project.go b/project/project.go new file mode 100644 index 000000000..5c4a7a112 --- /dev/null +++ b/project/project.go @@ -0,0 +1,279 @@ +// Package project finds and classifies Arduino projects. +package project + +import ( + "fmt" + + "github.com/arduino/arduino-check/configuration" + "github.com/arduino/arduino-check/project/library" + "github.com/arduino/arduino-check/project/packageindex" + "github.com/arduino/arduino-check/project/platform" + "github.com/arduino/arduino-check/project/projecttype" + "github.com/arduino/arduino-check/project/sketch" + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" +) + +// Type is the type for project definitions. +type Type struct { + Path *paths.Path + ProjectType projecttype.Type + SuperprojectType projecttype.Type +} + +// FindProjects searches the target path configured by the user for projects of the type configured by the user as well as the subprojects of those project. +// It returns a slice containing the definitions of each found project. +func FindProjects() ([]Type, error) { + targetPath := configuration.TargetPath() + superprojectTypeFilter := configuration.SuperprojectTypeFilter() + recursive := configuration.Recursive() + + var foundProjects []Type + + // If targetPath is a file, targetPath itself is the project, so it's only necessary to determine/verify the type. + if targetPath.IsNotDir() { + logrus.Debug("Projects path is file") + // The filename provides additional information about the project type. So rather than using isProject(), which doesn't make use this information, use a specialized function that does. + isProject, projectType := isProjectIndicatorFile(targetPath, superprojectTypeFilter) + if isProject { + foundProject := Type{ + Path: targetPath.Parent(), + ProjectType: projectType, + SuperprojectType: projectType, + } + foundProjects = append(foundProjects, foundProject) + + foundProjects = append(foundProjects, findSubprojects(foundProject, projectType)...) + + return foundProjects, nil + } + + return nil, fmt.Errorf("specified path %s is not an Arduino project", targetPath) + } + + foundProjects = append(foundProjects, findProjectsUnderPath(targetPath, superprojectTypeFilter, recursive)...) + + if foundProjects == nil { + return nil, fmt.Errorf("no projects found under %s", targetPath) + } + + return foundProjects, nil +} + +// findProjectsUnderPath finds projects of the given type and subprojects of those projects. It returns a slice containing the definitions of all found projects. +func findProjectsUnderPath(targetPath *paths.Path, projectTypeFilter projecttype.Type, recursive bool) []Type { + var foundProjects []Type + + isProject, foundProjectType := isProject(targetPath, projectTypeFilter) + if isProject { + logrus.Tracef("%s is %s", targetPath, foundProjectType) + foundProject := Type{ + Path: targetPath, + ProjectType: foundProjectType, + // findSubprojects() will overwrite this with the correct value when the project is a subproject. + SuperprojectType: foundProjectType, + } + foundProjects = append(foundProjects, foundProject) + + foundProjects = append(foundProjects, findSubprojects(foundProject, foundProject.ProjectType)...) + + // Don't search recursively past a project. + return foundProjects + } + + if recursive { + // targetPath was not a project, so search the subfolders. + directoryListing, _ := targetPath.ReadDir() + directoryListing.FilterDirs() + for _, potentialProjectDirectory := range directoryListing { + foundProjects = append(foundProjects, findProjectsUnderPath(potentialProjectDirectory, projectTypeFilter, recursive)...) + } + } + + return foundProjects +} + +// findSubprojects finds subprojects of the given project. +// For example, the subprojects of a library are its example sketches. +func findSubprojects(superproject Type, apexSuperprojectType projecttype.Type) []Type { + subprojectFolderNames := []string{} + var subProjectType projecttype.Type + var searchPathsRecursively bool + + // Determine possible subproject paths + switch superproject.ProjectType { + case projecttype.Sketch: + // Sketches don't have subprojects + return nil + case projecttype.Library: + subprojectFolderNames = append(subprojectFolderNames, library.ExamplesFolderSupportedNames()...) + subProjectType = projecttype.Sketch + searchPathsRecursively = true // Examples sketches can be under nested subfolders + case projecttype.Platform: + subprojectFolderNames = append(subprojectFolderNames, platform.BundledLibrariesFolderNames()...) + subProjectType = projecttype.Library + searchPathsRecursively = false // Bundled libraries must be in the root of the libraries folder + case projecttype.PackageIndex: + // Platform indexes don't have subprojects + return nil + default: + panic(fmt.Sprintf("Subproject discovery not configured for project type: %s", superproject.ProjectType)) + } + + // Search the subproject paths for projects + var immediateSubprojects []Type + for _, subprojectFolderName := range subprojectFolderNames { + subprojectPath := superproject.Path.Join(subprojectFolderName) + immediateSubprojects = append(immediateSubprojects, findProjectsUnderPath(subprojectPath, subProjectType, searchPathsRecursively)...) + } + + var allSubprojects []Type + // Subprojects may have their own subprojects. + for _, immediateSubproject := range immediateSubprojects { + // Subprojects at all levels should have SuperprojectType set to the top level superproject's type, not the immediate parent's type. + immediateSubproject.SuperprojectType = apexSuperprojectType + // Each parent project should be followed in the list by its subprojects. + allSubprojects = append(allSubprojects, immediateSubproject) + allSubprojects = append(allSubprojects, findSubprojects(immediateSubproject, apexSuperprojectType)...) + } + + return allSubprojects +} + +// isProject determines if a path contains an Arduino project, and if so which type. +func isProject(potentialProjectPath *paths.Path, projectTypeFilter projecttype.Type) (bool, projecttype.Type) { + logrus.Tracef("Checking if %s is %s", potentialProjectPath, projectTypeFilter) + + projectType := projecttype.Not + if projectTypeFilter.Matches(projecttype.Sketch) && isSketch(potentialProjectPath) { + projectType = projecttype.Sketch + } else if projectTypeFilter.Matches(projecttype.Library) && isLibrary(potentialProjectPath) { + projectType = projecttype.Library + } else if projectTypeFilter.Matches(projecttype.Platform) && isPlatform(potentialProjectPath) { + projectType = projecttype.Platform + } else if projectTypeFilter.Matches(projecttype.PackageIndex) && isPackageIndex(potentialProjectPath) { + projectType = projecttype.PackageIndex + } + + if projectType == projecttype.Not { + return false, projectType + } + logrus.Tracef("%s is %s", potentialProjectPath, projectType) + return true, projectType +} + +// isProject determines if a file is the indicator file for an Arduino project, and if so which type. +func isProjectIndicatorFile(potentialProjectFilePath *paths.Path, projectTypeFilter projecttype.Type) (bool, projecttype.Type) { + logrus.Tracef("Checking if %s is %s indicator file", potentialProjectFilePath, projectTypeFilter) + + projectType := projecttype.Not + if projectTypeFilter.Matches(projecttype.Sketch) && isSketchIndicatorFile(potentialProjectFilePath) { + projectType = projecttype.Sketch + } else if projectTypeFilter.Matches(projecttype.Library) && isLibraryIndicatorFile(potentialProjectFilePath) { + projectType = projecttype.Library + } else if projectTypeFilter.Matches(projecttype.Platform) && isPlatformIndicatorFile(potentialProjectFilePath) { + projectType = projecttype.Platform + } else if projectTypeFilter.Matches(projecttype.PackageIndex) && isPackageIndexIndicatorFile(potentialProjectFilePath) { + projectType = projecttype.PackageIndex + } + + if projectType == projecttype.Not { + logrus.Tracef("%s is not indicator file", potentialProjectFilePath) + return false, projectType + } + logrus.Tracef("%s is %s indicator file", potentialProjectFilePath, projectType) + return true, projectType +} + +// isSketch determines whether a path is an Arduino sketch. +// Note: this intentionally does not determine the validity of the sketch, only that the developer's intent was for it to be a sketch. +func isSketch(potentialProjectPath *paths.Path) bool { + directoryListing, _ := potentialProjectPath.ReadDir() + directoryListing.FilterOutDirs() + for _, potentialSketchFile := range directoryListing { + if isSketchIndicatorFile(potentialSketchFile) { + return true + } + } + + // No file was found with a valid main sketch file extension. + return false +} + +func isSketchIndicatorFile(filePath *paths.Path) bool { + return sketch.HasMainFileValidExtension(filePath) +} + +// isLibrary determines if a path is an Arduino library. +// Note: this intentionally does not determine the validity of the library, only that the developer's intent was for it to be a library. +func isLibrary(potentialProjectPath *paths.Path) bool { + // Arduino libraries will always have one of the following files in its root folder: + // - a library.properties metadata file + // - a header file + directoryListing, _ := potentialProjectPath.ReadDir() + directoryListing.FilterOutDirs() + for _, potentialLibraryFile := range directoryListing { + if isLibraryIndicatorFile(potentialLibraryFile) { + return true + } + } + + // None of the files required for a valid Arduino library were found. + return false +} + +func isLibraryIndicatorFile(filePath *paths.Path) bool { + if library.IsMetadataFile(filePath) { + return true + } + + if library.HasHeaderFileValidExtension(filePath) { + return true + } + + return false +} + +// isPlatform determines if a path is an Arduino boards platform. +// Note: this intentionally does not determine the validity of the platform, only that the developer's intent was for it to be a platform. +func isPlatform(potentialProjectPath *paths.Path) bool { + directoryListing, _ := potentialProjectPath.ReadDir() + directoryListing.FilterOutDirs() + for _, potentialPlatformFile := range directoryListing { + if isStrictPlatformIndicatorFile(potentialPlatformFile) { + return true + } + } + + return false +} + +func isPlatformIndicatorFile(filePath *paths.Path) bool { + return platform.IsConfigurationFile(filePath) +} + +func isStrictPlatformIndicatorFile(filePath *paths.Path) bool { + return platform.IsRequiredConfigurationFile(filePath) +} + +// isPackageIndex determines if a path contains an Arduino package index. +// Note: this intentionally does not determine the validity of the package index, only that the developer's intent was for it to be a package index. +func isPackageIndex(potentialProjectPath *paths.Path) bool { + directoryListing, _ := potentialProjectPath.ReadDir() + directoryListing.FilterOutDirs() + for _, potentialPackageIndexFile := range directoryListing { + if isStrictPackageIndexIndicatorFile(potentialPackageIndexFile) { + return true + } + } + + return false +} + +func isPackageIndexIndicatorFile(filePath *paths.Path) bool { + return packageindex.HasValidExtension(filePath) +} + +func isStrictPackageIndexIndicatorFile(filePath *paths.Path) bool { + return packageindex.HasValidFilename(filePath, true) +} diff --git a/project/projecttype/projecttype.go b/project/projecttype/projecttype.go new file mode 100644 index 000000000..96f300358 --- /dev/null +++ b/project/projecttype/projecttype.go @@ -0,0 +1,25 @@ +// Package projecttype defines the Arduino project types. +package projecttype + +// Type is the type for Arduino project types. +//go:generate stringer -type=Type -linecomment +type Type int + +const ( + Sketch Type = iota // sketch + Library // library + Platform // boards platform + PackageIndex // Boards Manager package index + All // any project type + Not // N/A +) + +// Matches returns whether the receiver project type matches the argument project type +func (projectTypeA Type) Matches(projectTypeB Type) bool { + if projectTypeA == Not && projectTypeB == Not { + return true + } else if projectTypeA == Not || projectTypeB == Not { + return false + } + return (projectTypeA == All || projectTypeB == All || projectTypeA == projectTypeB) +} diff --git a/project/projecttype/type_string.go b/project/projecttype/type_string.go new file mode 100644 index 000000000..8098ec4a8 --- /dev/null +++ b/project/projecttype/type_string.go @@ -0,0 +1,28 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package projecttype + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Sketch-0] + _ = x[Library-1] + _ = x[Platform-2] + _ = x[PackageIndex-3] + _ = x[All-4] + _ = x[Not-5] +} + +const _Type_name = "sketchlibraryboards platformBoards Manager package indexany project typeN/A" + +var _Type_index = [...]uint8{0, 6, 13, 28, 56, 72, 75} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/project/sketch/sketch.go b/project/sketch/sketch.go new file mode 100644 index 000000000..af05a809f --- /dev/null +++ b/project/sketch/sketch.go @@ -0,0 +1,17 @@ +/* +Package sketch provides functions specific to checking Arduino sketches. +See: https://arduino.github.io/arduino-cli/latest/sketch-specification/ +*/ +package sketch + +import ( + "github.com/arduino/arduino-cli/arduino/globals" + "github.com/arduino/go-paths-helper" +) + +// HasMainFileValidExtension returns whether the file at the given path has a valid sketch main file extension. +// Sketches may contain source files with other extensions (e.g., .h, .cpp), but they are required to have at least one file with a main extension. +func HasMainFileValidExtension(filePath *paths.Path) bool { + _, hasMainFileValidExtension := globals.MainFileValidExtensions[filePath.Ext()] + return hasMainFileValidExtension +} diff --git a/result/feedback/feedback.go b/result/feedback/feedback.go new file mode 100644 index 000000000..655fc5418 --- /dev/null +++ b/result/feedback/feedback.go @@ -0,0 +1,19 @@ +// Package feedback provides feedback to the user. +package feedback + +import ( + "fmt" + + "github.com/sirupsen/logrus" +) + +// Errorf behaves like fmt.Printf but also logs the error. +func Errorf(format string, v ...interface{}) { + Error(fmt.Sprintf(format, v...)) +} + +// Error behaves like fmt.Print but also logs the error. +func Error(errorMessage string) { + fmt.Printf(errorMessage) + logrus.Error(fmt.Sprint(errorMessage)) +} diff --git a/result/outputformat/outputformat.go b/result/outputformat/outputformat.go new file mode 100644 index 000000000..ca8cbe9ff --- /dev/null +++ b/result/outputformat/outputformat.go @@ -0,0 +1,11 @@ +// Package projecttype defines the output formats +package outputformat + +// Type is the type for output formats +//go:generate stringer -type=Type -linecomment +type Type int + +const ( + Text Type = iota // text + JSON // JSON +) diff --git a/result/outputformat/type_string.go b/result/outputformat/type_string.go new file mode 100644 index 000000000..98d3579c4 --- /dev/null +++ b/result/outputformat/type_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package outputformat + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Text-0] + _ = x[JSON-1] +} + +const _Type_name = "textJSON" + +var _Type_index = [...]uint8{0, 4, 8} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/result/result.go b/result/result.go new file mode 100644 index 000000000..54d5a0ead --- /dev/null +++ b/result/result.go @@ -0,0 +1,248 @@ +// Package result records check results and provides reports and summary text on those results. +package result + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + "os" + + "github.com/arduino/arduino-check/check/checkconfigurations" + "github.com/arduino/arduino-check/check/checklevel" + "github.com/arduino/arduino-check/check/checkresult" + "github.com/arduino/arduino-check/configuration" + "github.com/arduino/arduino-check/configuration/checkmode" + "github.com/arduino/arduino-check/project" + "github.com/arduino/arduino-check/result/feedback" + "github.com/arduino/go-paths-helper" +) + +// Results is the global instance of the check results result.Type struct +var Results Type + +// Type is the type for the check results data +type Type struct { + Configuration toolConfigurationReportType `json:"configuration"` + Projects []projectReportType `json:"projects"` + Summary summaryReportType `json:"summary"` +} + +type toolConfigurationReportType struct { + Paths []*paths.Path `json:"paths"` + ProjectType string `json:"projectType"` + Recursive bool `json:"recursive"` +} + +type projectReportType struct { + Path *paths.Path `json:"path"` + ProjectType string `json:"projectType"` + Configuration projectConfigurationReportType `json:"configuration"` + Checks []checkReportType `json:"checks"` + Summary summaryReportType `json:"summary"` +} + +type projectConfigurationReportType struct { + Permissive bool `json:"permissive"` + LibraryManagerSubmit bool `json:"libraryManagerSubmit"` + LibraryManagerUpdate bool `json:"libraryManagerUpdate"` + Official bool `json:"official"` +} + +type checkReportType struct { + Category string `json:"category"` + Subcategory string `json:"subcategory"` + ID string `json:"ID"` + Brief string `json:"brief"` + Description string `json:"description"` + Result string `json:"result"` + Level string `json:"level"` + Message string `json:"message"` +} + +type summaryReportType struct { + Pass bool `json:"pass"` + WarningCount int `json:"warningCount"` + ErrorCount int `json:"errorCount"` +} + +// Initialize adds the tool configuration data to the results data. +func (results *Type) Initialize() { + results.Configuration = toolConfigurationReportType{ + Paths: []*paths.Path{configuration.TargetPath()}, + ProjectType: configuration.SuperprojectTypeFilter().String(), + Recursive: configuration.Recursive(), + } +} + +// Record records the result of a check and returns a text summary for it. +func (results *Type) Record(checkedProject project.Type, checkConfiguration checkconfigurations.Type, checkResult checkresult.Type, checkOutput string) string { + checkMessage := message(checkConfiguration.MessageTemplate, checkOutput) + + checkLevel, err := checklevel.CheckLevel(checkConfiguration) + if err != nil { + feedback.Errorf("Error while determining check level: %v", err) + os.Exit(1) + } + + summaryText := fmt.Sprintf("%s\n", checkResult) + + if checkResult == checkresult.NotRun { + // TODO: make the check functions output an explanation for why they didn't run + summaryText += fmt.Sprintf("%s: %s\n", checklevel.Notice, checkOutput) + } else if checkResult != checkresult.Pass { + summaryText += fmt.Sprintf("%s: %s\n", checkLevel, checkMessage) + } + + checkReport := checkReportType{ + Category: checkConfiguration.Category, + Subcategory: checkConfiguration.Subcategory, + ID: checkConfiguration.ID, + Brief: checkConfiguration.Brief, + Description: checkConfiguration.Description, + Result: checkResult.String(), + Level: checkLevel.String(), + Message: checkMessage, + } + + reportExists, projectReportIndex := results.getProjectReportIndex(checkedProject.Path) + if !reportExists { + // There is no existing report for this project. + results.Projects = append( + results.Projects, + projectReportType{ + Path: checkedProject.Path, + ProjectType: checkedProject.ProjectType.String(), + Configuration: projectConfigurationReportType{ + Permissive: configuration.CheckModes(checkedProject.ProjectType)[checkmode.Permissive], + LibraryManagerSubmit: configuration.CheckModes(checkedProject.ProjectType)[checkmode.Permissive], + LibraryManagerUpdate: configuration.CheckModes(checkedProject.ProjectType)[checkmode.LibraryManagerIndexed], + Official: configuration.CheckModes(checkedProject.ProjectType)[checkmode.Official], + }, + Checks: []checkReportType{checkReport}, + }, + ) + } else { + // There's already a report for this project, just add the checks report to it + results.Projects[projectReportIndex].Checks = append(results.Projects[projectReportIndex].Checks, checkReport) + } + + return summaryText +} + +// AddProjectSummary summarizes the results of all checks on the given project and adds it to the report. +func (results *Type) AddProjectSummary(checkedProject project.Type) { + reportExists, projectReportIndex := results.getProjectReportIndex(checkedProject.Path) + if !reportExists { + panic(fmt.Sprintf("Unable to find report for %v when generating report summary", checkedProject.Path)) + } + + pass := true + warningCount := 0 + errorCount := 0 + for _, checkReport := range results.Projects[projectReportIndex].Checks { + if checkReport.Result == checkresult.Fail.String() { + if checkReport.Level == checklevel.Warning.String() { + warningCount += 1 + } else if checkReport.Level == checklevel.Error.String() { + errorCount += 1 + pass = false + } + } + } + + results.Projects[projectReportIndex].Summary = summaryReportType{ + Pass: pass, + WarningCount: warningCount, + ErrorCount: errorCount, + } +} + +// ProjectSummaryText returns a text summary of the check results for the given project. +func (results Type) ProjectSummaryText(checkedProject project.Type) string { + reportExists, projectReportIndex := results.getProjectReportIndex(checkedProject.Path) + if !reportExists { + panic(fmt.Sprintf("Unable to find report for %v when generating report summary text", checkedProject.Path)) + } + + projectSummaryReport := results.Projects[projectReportIndex].Summary + return fmt.Sprintf("\nFinished checking project. Results:\nWarning count: %v\nError count: %v\nChecks passed: %v\n\n", projectSummaryReport.WarningCount, projectSummaryReport.ErrorCount, projectSummaryReport.Pass) +} + +// AddSummary summarizes the check results for all projects and adds it to the report. +func (results *Type) AddSummary() { + pass := true + warningCount := 0 + errorCount := 0 + for _, projectReport := range results.Projects { + if !projectReport.Summary.Pass { + pass = false + } + warningCount += projectReport.Summary.WarningCount + errorCount += projectReport.Summary.ErrorCount + } + + results.Summary = summaryReportType{ + Pass: pass, + WarningCount: warningCount, + ErrorCount: errorCount, + } +} + +// SummaryText returns a text summary of the cumulative check results. +func (results Type) SummaryText() string { + return fmt.Sprintf("Finished checking projects. Results:\nWarning count: %v\nError count: %v\nChecks passed: %v\n", results.Summary.WarningCount, results.Summary.ErrorCount, results.Summary.Pass) +} + +// JSONReport returns a JSON formatted report of checks on all projects. +func (results Type) JSONReport() string { + return string(results.jsonReportRaw()) +} + +func (results Type) jsonReportRaw() []byte { + reportJSON, err := json.MarshalIndent(results, "", " ") + if err != nil { + panic(fmt.Sprintf("Error while formatting checks report: %v", err)) + } + + return reportJSON +} + +// WriteReport writes a report for all projects to the specified file. +func (results Type) WriteReport() { + // Write report file + err := configuration.ReportFilePath().WriteFile(results.jsonReportRaw()) + if err != nil { + feedback.Errorf("Error while writing report: %v", err) + os.Exit(1) + } +} + +// Passed returns whether the checks passed cumulatively. +func (results Type) Passed() bool { + return results.Summary.Pass +} + +func (results Type) getProjectReportIndex(projectPath *paths.Path) (bool, int) { + var index int + var projectReport projectReportType + for index, projectReport = range results.Projects { + if projectReport.Path == projectPath { + return true, index + } + } + + // There is no element in the report for this project. + return false, index + 1 +} + +// message fills the message template provided by the check configuration with the check output. +// TODO: make checkOutput a struct to allow for more advanced message templating +func message(templateText string, checkOutput string) string { + messageTemplate := template.Must(template.New("messageTemplate").Parse(templateText)) + + messageBuffer := new(bytes.Buffer) + messageTemplate.Execute(messageBuffer, checkOutput) + + return messageBuffer.String() +}