Skip to content

Commit ce7a60f

Browse files
committed
Add rules for the platform.txt pluggable_discovery.* properties
The new pluggable discovery system for Arduino boards platforms introduces a new set of `pluggable_discovery.*` properties to the platform.txt configuration file, which have requirements to be enforced via Arduino Lint rules.
1 parent 72520b3 commit ce7a60f

File tree

26 files changed

+1094
-18
lines changed

26 files changed

+1094
-18
lines changed

etc/schemas/arduino-platform-txt-definitions-schema.json

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,215 @@
700700
}
701701
}
702702
},
703+
"pluggableDiscovery": {
704+
"base": {
705+
"object": {
706+
"allOf": [
707+
{
708+
"type": "object"
709+
}
710+
]
711+
}
712+
},
713+
"permissive": {
714+
"object": {
715+
"allOf": [
716+
{
717+
"$ref": "#/definitions/propertiesObjects/pluggableDiscovery/base/object"
718+
},
719+
{
720+
"properties": {
721+
"required": {
722+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/permissive/object"
723+
}
724+
},
725+
"additionalProperties": {
726+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/permissive/object"
727+
}
728+
}
729+
]
730+
}
731+
},
732+
"specification": {
733+
"object": {
734+
"allOf": [
735+
{
736+
"$ref": "#/definitions/propertiesObjects/pluggableDiscovery/base/object"
737+
},
738+
{
739+
"properties": {
740+
"required": {
741+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/specification/object"
742+
}
743+
},
744+
"additionalProperties": {
745+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/specification/object"
746+
}
747+
}
748+
]
749+
}
750+
},
751+
"strict": {
752+
"object": {
753+
"allOf": [
754+
{
755+
"$ref": "#/definitions/propertiesObjects/pluggableDiscovery/base/object"
756+
},
757+
{
758+
"properties": {
759+
"required": {
760+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/strict/object"
761+
}
762+
},
763+
"additionalProperties": {
764+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/strict/object"
765+
}
766+
}
767+
]
768+
}
769+
}
770+
},
771+
"pluggableDiscoveryDiscoveryName": {
772+
"base": {
773+
"object": {
774+
"allOf": [
775+
{
776+
"type": "object"
777+
}
778+
]
779+
}
780+
},
781+
"permissive": {
782+
"object": {
783+
"allOf": [
784+
{
785+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/base/object"
786+
},
787+
{
788+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/permissive/object"
789+
}
790+
]
791+
}
792+
},
793+
"specification": {
794+
"object": {
795+
"allOf": [
796+
{
797+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/base/object"
798+
},
799+
{
800+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/specification/object"
801+
}
802+
]
803+
}
804+
},
805+
"strict": {
806+
"object": {
807+
"allOf": [
808+
{
809+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryDiscoveryName/base/object"
810+
},
811+
{
812+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/strict/object"
813+
}
814+
]
815+
}
816+
}
817+
},
818+
"pluggableDiscoveryRequired": {
819+
"base": {
820+
"object": {
821+
"allOf": [
822+
{
823+
"type": "array"
824+
}
825+
]
826+
}
827+
},
828+
"permissive": {
829+
"object": {
830+
"allOf": [
831+
{
832+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/base/object"
833+
},
834+
{
835+
"items": {
836+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/permissive/object"
837+
}
838+
}
839+
]
840+
}
841+
},
842+
"specification": {
843+
"object": {
844+
"allOf": [
845+
{
846+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/base/object"
847+
},
848+
{
849+
"items": {
850+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/specification/object"
851+
}
852+
}
853+
]
854+
}
855+
},
856+
"strict": {
857+
"object": {
858+
"allOf": [
859+
{
860+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequired/base/object"
861+
},
862+
{
863+
"items": {
864+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/strict/object"
865+
}
866+
}
867+
]
868+
}
869+
}
870+
},
871+
"pluggableDiscoveryRequiredN": {
872+
"base": {
873+
"object": {
874+
"allOf": [
875+
{
876+
"type": "string"
877+
},
878+
{
879+
"pattern": "^.+:.+$"
880+
}
881+
]
882+
}
883+
},
884+
"permissive": {
885+
"object": {
886+
"allOf": [
887+
{
888+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/base/object"
889+
}
890+
]
891+
}
892+
},
893+
"specification": {
894+
"object": {
895+
"allOf": [
896+
{
897+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/base/object"
898+
}
899+
]
900+
}
901+
},
902+
"strict": {
903+
"object": {
904+
"allOf": [
905+
{
906+
"$ref": "#/definitions/propertiesObjects/pluggableDiscoveryRequiredN/base/object"
907+
}
908+
]
909+
}
910+
}
911+
},
703912
"tools": {
704913
"base": {
705914
"object": {
@@ -1145,6 +1354,44 @@
11451354
}
11461355
}
11471356
},
1357+
"pluggableDiscoveryDiscoveryName": {
1358+
"base": {
1359+
"object": {
1360+
"allOf": [
1361+
{
1362+
"required": ["pattern"]
1363+
}
1364+
]
1365+
}
1366+
},
1367+
"permissive": {
1368+
"object": {
1369+
"allOf": [
1370+
{
1371+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/base/object"
1372+
}
1373+
]
1374+
}
1375+
},
1376+
"specification": {
1377+
"object": {
1378+
"allOf": [
1379+
{
1380+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/base/object"
1381+
}
1382+
]
1383+
}
1384+
},
1385+
"strict": {
1386+
"object": {
1387+
"allOf": [
1388+
{
1389+
"$ref": "#/definitions/requiredObjects/pluggableDiscoveryDiscoveryName/base/object"
1390+
}
1391+
]
1392+
}
1393+
}
1394+
},
11481395
"toolsToolNameActionName": {
11491396
"base": {
11501397
"object": {

etc/schemas/arduino-platform-txt-permissive-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"recipe.size.pattern": {
5555
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/recipeSizePattern/permissive/object"
5656
},
57+
"pluggable_discovery": {
58+
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/pluggableDiscovery/permissive/object"
59+
},
5760
"tools": {
5861
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/tools/permissive/object"
5962
}

etc/schemas/arduino-platform-txt-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"recipe.size.pattern": {
5555
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/recipeSizePattern/specification/object"
5656
},
57+
"pluggable_discovery": {
58+
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/pluggableDiscovery/specification/object"
59+
},
5760
"tools": {
5861
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/tools/specification/object"
5962
}

etc/schemas/arduino-platform-txt-strict-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"recipe.size.pattern": {
5555
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/recipeSizePattern/strict/object"
5656
},
57+
"pluggable_discovery": {
58+
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/pluggableDiscovery/strict/object"
59+
},
5760
"tools": {
5861
"$ref": "arduino-platform-txt-definitions-schema.json#/definitions/propertiesObjects/tools/strict/object"
5962
}

internal/project/platform/platformtxt/platformtxt.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,20 @@ func Validate(platformTxt *properties.Map) map[compliancelevel.Type]schema.Valid
5757
validation package.
5858
Even though platform.txt has a multi-level nested data structure, the format has the odd characteristic of allowing
5959
a key to be both an object and a string simultaneously, which is not compatible with Golang maps or JSON. So the
60-
data structure used is a selective map, using a flat map except for the tools key, which can contain any number of
61-
arbitrary tool name subproperties which must be linted.
60+
data structure used is a selective map, using a flat map except for the tools and pluggable_discovery keys, which
61+
can contain any number of arbitrary subproperties which must be linted.
6262
*/
6363
platformTxtInterface := make(map[string]interface{})
6464
keys := platformTxt.Keys()
6565
for _, key := range keys {
66-
if strings.HasPrefix(key, "tools.") {
66+
if strings.HasPrefix(key, "pluggable_discovery.") {
67+
if key == "pluggable_discovery.required" || strings.HasPrefix(key, "pluggable_discovery.required.") {
68+
platformTxtInterface["pluggable_discovery"] = general.PropertiesToList(platformTxt.SubTree("pluggable_discovery"), "required")
69+
} else {
70+
// It is a pluggable_discovery.DISCOVERY_ID property.
71+
platformTxtInterface["pluggable_discovery"] = general.PropertiesToMap(platformTxt.SubTree("pluggable_discovery"), 2)
72+
}
73+
} else if strings.HasPrefix(key, "tools.") {
6774
platformTxtInterface["tools"] = general.PropertiesToMap(platformTxt.SubTree("tools"), 3)
6875
} else {
6976
platformTxtInterface[key] = platformTxt.Get(key)
@@ -77,6 +84,18 @@ func Validate(platformTxt *properties.Map) map[compliancelevel.Type]schema.Valid
7784
return validationResults
7885
}
7986

87+
// PluggableDiscoveryNames returns the list of pluggable discovery names from the given platform.txt properties.
88+
func PluggableDiscoveryNames(platformTxt *properties.Map) []string {
89+
names := platformTxt.SubTree("pluggable_discovery").FirstLevelKeys()
90+
for i := range names {
91+
for i < len(names) && names[i] == "required" {
92+
names = append(names[:i], names[i+1:]...)
93+
}
94+
}
95+
96+
return names
97+
}
98+
8099
// ToolNames returns the list of tool names from the given platform.txt properties.
81100
func ToolNames(platformTxt *properties.Map) []string {
82101
return platformTxt.SubTree("tools").FirstLevelKeys()

internal/project/platform/platformtxt/platformtxt_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ func init() {
6262
"recipe.size.pattern": "asdf",
6363
"recipe.size.regex": "asdf",
6464
"recipe.size.regex.data": "asdf",
65+
"pluggable_discovery.required.0": "builtin:serial-discovery",
66+
"pluggable_discovery.required.1": "builtin:mdns-discovery",
6567
"tools.avrdude.upload.params.verbose": "-v",
6668
"tools.avrdude.upload.params.quiet": "-q -q",
6769
"tools.avrdude.upload.pattern": "asdf",
@@ -90,6 +92,16 @@ func TestValidate(t *testing.T) {
9092
assert.NotNil(t, validationResult[compliancelevel.Strict].Result, "Invalid (strict)")
9193
}
9294

95+
func TestPluggableDiscoveryNames(t *testing.T) {
96+
platformTxt := properties.NewFromHashmap(validPlatformTxtMap)
97+
98+
assert.ElementsMatch(t, []string{}, PluggableDiscoveryNames(platformTxt), "No elements for pluggable_discovery.required properties.")
99+
100+
platformTxt.Set("pluggable_discovery.foo_discovery.pattern", "asdf")
101+
platformTxt.Set("pluggable_discovery.bar_discovery.pattern", "zxcv")
102+
assert.ElementsMatch(t, []string{"foo_discovery", "bar_discovery"}, PluggableDiscoveryNames(platformTxt), "pluggable_discovery.DISCOVERY_ID properties add elements for each DISCOVERY_ID.")
103+
}
104+
93105
func TestToolNames(t *testing.T) {
94106
platformTxt := properties.NewFromHashmap(validPlatformTxtMap)
95107

internal/project/platform/platformtxt/platformtxtschema_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@ func TestRequired(t *testing.T) {
240240
{"compiler.ar.extra_flags", "", "compiler.ar.extra_flags", compliancelevel.Specification, assert.False},
241241
{"compiler.ar.extra_flags", "", "compiler.ar.extra_flags", compliancelevel.Strict, assert.True},
242242

243+
{"pluggable_discovery.foo.pattern", "pluggable_discovery.foo.bar", "pluggable_discovery/foo/pattern", compliancelevel.Permissive, assert.True},
244+
{"pluggable_discovery.foo.pattern", "pluggable_discovery.foo.bar", "pluggable_discovery/foo/pattern", compliancelevel.Specification, assert.True},
245+
{"pluggable_discovery.foo.pattern", "pluggable_discovery.foo.bar", "pluggable_discovery/foo/pattern", compliancelevel.Strict, assert.True},
246+
// Property is only required when there is a pluggable_discovery.foo object
247+
{"pluggable_discovery.foo.pattern", "", "pluggable_discovery/foo/pattern", compliancelevel.Permissive, assert.False},
248+
{"pluggable_discovery.foo.pattern", "", "pluggable_discovery/foo/pattern", compliancelevel.Specification, assert.False},
249+
{"pluggable_discovery.foo.pattern", "", "pluggable_discovery/foo/pattern", compliancelevel.Strict, assert.False},
250+
243251
{"recipe.size.pattern", "", "recipe.size.pattern", compliancelevel.Permissive, assert.False},
244252
{"recipe.size.pattern", "", "recipe.size.pattern", compliancelevel.Specification, assert.False},
245253
{"recipe.size.pattern", "", "recipe.size.pattern", compliancelevel.Strict, assert.True},
@@ -387,6 +395,19 @@ func TestPattern(t *testing.T) {
387395
{"recipe.preproc.macros", "recipe\\.preproc\\.macros", "foo", compliancelevel.Permissive, assert.False},
388396
{"recipe.preproc.macros", "recipe\\.preproc\\.macros", "foo", compliancelevel.Specification, assert.False},
389397
{"recipe.preproc.macros", "recipe\\.preproc\\.macros", "foo", compliancelevel.Strict, assert.True},
398+
399+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo:bar", compliancelevel.Permissive, assert.False},
400+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo:bar", compliancelevel.Specification, assert.False},
401+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo:bar", compliancelevel.Strict, assert.False},
402+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo", compliancelevel.Permissive, assert.True},
403+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo", compliancelevel.Specification, assert.True},
404+
{"pluggable_discovery.required", "pluggable_discovery/required", "foo", compliancelevel.Strict, assert.True},
405+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo:bar", compliancelevel.Permissive, assert.False},
406+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo:bar", compliancelevel.Specification, assert.False},
407+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo:bar", compliancelevel.Strict, assert.False},
408+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo", compliancelevel.Permissive, assert.True},
409+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo", compliancelevel.Specification, assert.True},
410+
{"pluggable_discovery.required.1", "pluggable_discovery/required", "foo", compliancelevel.Strict, assert.True},
390411
}
391412

392413
for _, testTable := range testTables {

0 commit comments

Comments
 (0)