Skip to content

Commit c7182c1

Browse files
committed
Add rules for user defined fields of Arduino platforms
Some upload recipes might require custom fields that must be provided by the user. The platform author can define such fields via two new property types that have been added to the Arduino platform system with the expanded pluggable discovery support. There are some requirements for these fields, which are enforced via a couple of new rules.
1 parent 1bdac6c commit c7182c1

File tree

20 files changed

+900
-6
lines changed

20 files changed

+900
-6
lines changed

Diff for: etc/schemas/arduino-platform-txt-definitions-schema.json

+171
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,13 @@
10751075
},
10761076
{
10771077
"$ref": "#/definitions/requiredObjects/toolsToolNameUpload/permissive/object"
1078+
},
1079+
{
1080+
"properties": {
1081+
"field": {
1082+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/permissive/object"
1083+
}
1084+
}
10781085
}
10791086
]
10801087
}
@@ -1087,6 +1094,13 @@
10871094
},
10881095
{
10891096
"$ref": "#/definitions/requiredObjects/toolsToolNameUpload/specification/object"
1097+
},
1098+
{
1099+
"properties": {
1100+
"field": {
1101+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/specification/object"
1102+
}
1103+
}
10901104
}
10911105
]
10921106
}
@@ -1099,6 +1113,163 @@
10991113
},
11001114
{
11011115
"$ref": "#/definitions/requiredObjects/toolsToolNameUpload/strict/object"
1116+
},
1117+
{
1118+
"properties": {
1119+
"field": {
1120+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/strict/object"
1121+
}
1122+
}
1123+
}
1124+
]
1125+
}
1126+
}
1127+
},
1128+
"toolsToolNameUploadField": {
1129+
"base": {
1130+
"object": {
1131+
"allOf": [
1132+
{
1133+
"type": "object"
1134+
}
1135+
]
1136+
}
1137+
},
1138+
"permissive": {
1139+
"object": {
1140+
"allOf": [
1141+
{
1142+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/base/object"
1143+
},
1144+
{
1145+
"patternProperties": {
1146+
"^.+([^.].*|\\.([^s].*)?|\\.se([^c].*)?|\\.sec([^r].*)?|\\.secr([^e].*)?|\\.secre([^t].*)?|\\.secret.+)$": {
1147+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/permissive/object"
1148+
},
1149+
"^.+\\.secret$": {
1150+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/permissive/object"
1151+
}
1152+
}
1153+
}
1154+
]
1155+
}
1156+
},
1157+
"specification": {
1158+
"object": {
1159+
"allOf": [
1160+
{
1161+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/base/object"
1162+
},
1163+
{
1164+
"patternProperties": {
1165+
"^.+([^.].*|\\.([^s].*)?|\\.se([^c].*)?|\\.sec([^r].*)?|\\.secr([^e].*)?|\\.secre([^t].*)?|\\.secret.+)$": {
1166+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/specification/object"
1167+
},
1168+
"^.+\\.secret$": {
1169+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/specification/object"
1170+
}
1171+
}
1172+
}
1173+
]
1174+
}
1175+
},
1176+
"strict": {
1177+
"object": {
1178+
"allOf": [
1179+
{
1180+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadField/base/object"
1181+
},
1182+
{
1183+
"patternProperties": {
1184+
"^.+([^.].*|\\.([^s].*)?|\\.se([^c].*)?|\\.sec([^r].*)?|\\.secr([^e].*)?|\\.secre([^t].*)?|\\.secret.+)$": {
1185+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/strict/object"
1186+
},
1187+
"^.+\\.secret$": {
1188+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/strict/object"
1189+
}
1190+
}
1191+
}
1192+
]
1193+
}
1194+
}
1195+
},
1196+
"toolsToolNameUploadFieldFieldName": {
1197+
"base": {
1198+
"object": {
1199+
"allOf": [
1200+
{
1201+
"type": "string"
1202+
}
1203+
]
1204+
}
1205+
},
1206+
"permissive": {
1207+
"object": {
1208+
"allOf": [
1209+
{
1210+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/base/object"
1211+
}
1212+
]
1213+
}
1214+
},
1215+
"specification": {
1216+
"object": {
1217+
"allOf": [
1218+
{
1219+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/base/object"
1220+
},
1221+
{
1222+
"maxLength": 50
1223+
}
1224+
]
1225+
}
1226+
},
1227+
"strict": {
1228+
"object": {
1229+
"allOf": [
1230+
{
1231+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldName/specification/object"
1232+
}
1233+
]
1234+
}
1235+
}
1236+
},
1237+
"toolsToolNameUploadFieldFieldNameSecret": {
1238+
"base": {
1239+
"object": {
1240+
"allOf": [
1241+
{
1242+
"type": "string"
1243+
},
1244+
{
1245+
"$ref": "general-definitions-schema.json#/definitions/enumObjects/booleanString"
1246+
}
1247+
]
1248+
}
1249+
},
1250+
"permissive": {
1251+
"object": {
1252+
"allOf": [
1253+
{
1254+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/base/object"
1255+
}
1256+
]
1257+
}
1258+
},
1259+
"specification": {
1260+
"object": {
1261+
"allOf": [
1262+
{
1263+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/base/object"
1264+
}
1265+
]
1266+
}
1267+
},
1268+
"strict": {
1269+
"object": {
1270+
"allOf": [
1271+
{
1272+
"$ref": "#/definitions/propertiesObjects/toolsToolNameUploadFieldFieldNameSecret/base/object"
11021273
}
11031274
]
11041275
}

Diff for: internal/project/platform/platformtxt/platformtxt.go

+15
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ See: https://arduino.github.io/arduino-cli/latest/platform-specification/#platfo
2020
package platformtxt
2121

2222
import (
23+
"fmt"
2324
"strings"
2425

2526
"github.com/arduino/arduino-lint/internal/project/general"
@@ -96,6 +97,20 @@ func PluggableDiscoveryNames(platformTxt *properties.Map) []string {
9697
return names
9798
}
9899

100+
// UserProvidedFieldNames returns the list of user provided field names platform.txt properties, mapped by tool name.
101+
func UserProvidedFieldNames(platformTxt *properties.Map) map[string][]string {
102+
fieldNames := make(map[string][]string)
103+
toolsProps := platformTxt.SubTree("tools")
104+
for _, tool := range toolsProps.FirstLevelKeys() {
105+
fieldProps := toolsProps.SubTree(fmt.Sprintf("%s.upload.field", tool))
106+
for _, fieldName := range fieldProps.FirstLevelKeys() {
107+
fieldNames[tool] = append(fieldNames[tool], fieldName)
108+
}
109+
}
110+
111+
return fieldNames
112+
}
113+
99114
// ToolNames returns the list of tool names from the given platform.txt properties.
100115
func ToolNames(platformTxt *properties.Map) []string {
101116
return platformTxt.SubTree("tools").FirstLevelKeys()

Diff for: internal/project/platform/platformtxt/platformtxt_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package platformtxt
1717

1818
import (
19+
"reflect"
1920
"testing"
2021

2122
"github.com/arduino/arduino-lint/internal/rule/schema/compliancelevel"
@@ -102,6 +103,27 @@ func TestPluggableDiscoveryNames(t *testing.T) {
102103
assert.ElementsMatch(t, []string{"foo_discovery", "bar_discovery"}, PluggableDiscoveryNames(platformTxt), "pluggable_discovery.DISCOVERY_ID properties add elements for each DISCOVERY_ID.")
103104
}
104105

106+
func TestUserProvidedFieldNames(t *testing.T) {
107+
platformTxt := properties.NewFromHashmap(validPlatformTxtMap)
108+
109+
assertion := make(map[string][]string)
110+
assert.True(t, reflect.DeepEqual(assertion, UserProvidedFieldNames(platformTxt)))
111+
112+
platformTxt.Set("tools.avrdude.upload.field.foo_field_name", "Some field label")
113+
assertion = map[string][]string{
114+
"avrdude": {"foo_field_name"},
115+
}
116+
assert.True(t, reflect.DeepEqual(assertion, UserProvidedFieldNames(platformTxt)))
117+
118+
platformTxt.Set("tools.avrdude.upload.field.bar_field_name", "Some field label")
119+
platformTxt.Set("tools.bossac.upload.field.baz_field_name", "Some field label")
120+
assertion = map[string][]string{
121+
"avrdude": {"foo_field_name", "bar_field_name"},
122+
"bossac": {"baz_field_name"},
123+
}
124+
assert.True(t, reflect.DeepEqual(assertion, UserProvidedFieldNames(platformTxt)))
125+
}
126+
105127
func TestToolNames(t *testing.T) {
106128
platformTxt := properties.NewFromHashmap(validPlatformTxtMap)
107129

Diff for: internal/project/platform/platformtxt/platformtxtschema_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ var validPlatformTxtRaw = []byte(`
5757
recipe.size.regex.data=asdf
5858
tools.avrdude.upload.params.verbose=-v
5959
tools.avrdude.upload.params.quiet=-q -q
60+
tools.avrdude.upload.field.foo_field_name=Some field label
61+
tools.avrdude.upload.field.foo_field_name.secret=true
6062
tools.avrdude.upload.pattern=asdf
6163
tools.avrdude.program.params.verbose=-v
6264
tools.avrdude.program.params.quiet=-q -q
@@ -144,6 +146,38 @@ func TestMinLength(t *testing.T) {
144146
}
145147
}
146148

149+
func TestMaxLength(t *testing.T) {
150+
testTables := []struct {
151+
propertyName string
152+
validationErrorPropertyName string
153+
maxLength int
154+
complianceLevel compliancelevel.Type
155+
}{
156+
{"tools.avrdude.upload.field.foo_field_name", "tools/avrdude/upload/field/foo_field_name", 50, compliancelevel.Specification},
157+
{"tools.avrdude.upload.field.foo_field_name", "tools/avrdude/upload/field/foo_field_name", 50, compliancelevel.Strict},
158+
}
159+
160+
// Test schema validation results with value length > maximum.
161+
for _, testTable := range testTables {
162+
platformTxt, err := properties.LoadFromBytes(validPlatformTxtRaw)
163+
require.Nil(t, err)
164+
platformTxt.Set(testTable.propertyName, strings.Repeat("a", testTable.maxLength+1))
165+
166+
t.Run(fmt.Sprintf("%s greater than maximum length of %d (%s)", testTable.propertyName, testTable.maxLength, testTable.complianceLevel), func(t *testing.T) {
167+
assert.True(t, schema.PropertyGreaterThanMaxLength(testTable.validationErrorPropertyName, platformtxt.Validate(platformTxt)[testTable.complianceLevel]))
168+
})
169+
170+
// Test schema validation results with maximum value length.
171+
platformTxt, err = properties.LoadFromBytes(validPlatformTxtRaw)
172+
require.Nil(t, err)
173+
platformTxt.Set(testTable.propertyName, strings.Repeat("a", testTable.maxLength))
174+
175+
t.Run(fmt.Sprintf("%s at maximum length of %d (%s)", testTable.propertyName, testTable.maxLength, testTable.complianceLevel), func(t *testing.T) {
176+
assert.False(t, schema.PropertyGreaterThanMaxLength(testTable.validationErrorPropertyName, platformtxt.Validate(platformTxt)[testTable.complianceLevel]))
177+
})
178+
}
179+
}
180+
147181
func TestRequired(t *testing.T) {
148182
testTables := []struct {
149183
propertyName string
@@ -318,6 +352,16 @@ func TestEnum(t *testing.T) {
318352
{"compiler.ar.extra_flags", "compiler\\.ar\\.extra_flags", "foo", compliancelevel.Permissive, assert.False},
319353
{"compiler.ar.extra_flags", "compiler\\.ar\\.extra_flags", "foo", compliancelevel.Specification, assert.False},
320354
{"compiler.ar.extra_flags", "compiler\\.ar\\.extra_flags", "foo", compliancelevel.Strict, assert.True},
355+
356+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "true", compliancelevel.Permissive, assert.False},
357+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "true", compliancelevel.Specification, assert.False},
358+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "true", compliancelevel.Strict, assert.False},
359+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "false", compliancelevel.Permissive, assert.False},
360+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "false", compliancelevel.Specification, assert.False},
361+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "false", compliancelevel.Strict, assert.False},
362+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "foo", compliancelevel.Permissive, assert.True},
363+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "foo", compliancelevel.Specification, assert.True},
364+
{"tools.avrdude.upload.field.foo_field_name.secret", "tools/avrdude/upload/field/foo_field_name\\.secret", "foo", compliancelevel.Strict, assert.True},
321365
}
322366

323367
for _, testTable := range testTables {

Diff for: internal/project/projectdata/platform.go

+9
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ func InitializeForPlatform(project project.Type) {
5959
logrus.Tracef("Error loading platform.txt from %s: %s", project.Path, platformTxtLoadError)
6060
platformTxtSchemaValidationResult = nil
6161
platformTxtPluggableDiscoveryNames = nil
62+
platformTxtUserProvidedFieldNames = nil
6263
platformTxtToolNames = nil
6364
} else {
6465
platformTxtSchemaValidationResult = platformtxt.Validate(platformTxt)
6566

6667
platformTxtPluggableDiscoveryNames = platformtxt.PluggableDiscoveryNames(platformTxt)
68+
platformTxtUserProvidedFieldNames = platformtxt.UserProvidedFieldNames(platformTxt)
6769
platformTxtToolNames = platformtxt.ToolNames(platformTxt)
6870
}
6971
}
@@ -180,6 +182,13 @@ func PlatformTxtPluggableDiscoveryNames() []string {
180182
return platformTxtPluggableDiscoveryNames
181183
}
182184

185+
var platformTxtUserProvidedFieldNames map[string][]string
186+
187+
// PlatformTxtUserProvidedFieldNames returns the list of user provided field names present in the platform's platform.txt, mapped by board name.
188+
func PlatformTxtUserProvidedFieldNames() map[string][]string {
189+
return platformTxtUserProvidedFieldNames
190+
}
191+
183192
var platformTxtToolNames []string
184193

185194
// PlatformTxtToolNames returns the list of tools present in the platform's platform.txt.

Diff for: internal/project/projectdata/platform_test.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package projectdata
1717

1818
import (
19+
"reflect"
1920
"testing"
2021

2122
"github.com/arduino/arduino-lint/internal/project"
@@ -45,14 +46,15 @@ func TestInitializeForPlatform(t *testing.T) {
4546
platformTxtLoadErrorAssertion assert.ValueAssertionFunc
4647
platformTxtSchemaValidationResultAssertion assert.ValueAssertionFunc
4748
platformTxtPluggableDiscoveryNamesAssertion []string
49+
platformTxtUserProvidedFieldNamesAssertion map[string][]string
4850
platformTxtToolNamesAssertion []string
4951
}{
50-
{"Valid boards.txt", "valid-boards.txt", assert.NotNil, assert.Nil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil},
51-
{"Invalid boards.txt", "invalid-boards.txt", assert.Nil, assert.NotNil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil},
52-
{"Missing boards.txt", "missing-boards.txt", assert.Nil, assert.NotNil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil},
53-
{"Valid platform.txt", "valid-platform.txt", assert.NotNil, assert.Nil, assert.True, assert.NotNil, assert.Nil, assert.NotNil, []string{"foo_discovery", "bar_discovery"}, []string{"avrdude", "bossac"}},
54-
{"Invalid platform.txt", "invalid-platform.txt", assert.NotNil, assert.Nil, assert.True, assert.Nil, assert.NotNil, assert.Nil, nil, nil},
55-
{"Missing platform.txt", "missing-platform.txt", assert.NotNil, assert.Nil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil},
52+
{"Valid boards.txt", "valid-boards.txt", assert.NotNil, assert.Nil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil, nil},
53+
{"Invalid boards.txt", "invalid-boards.txt", assert.Nil, assert.NotNil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil, nil},
54+
{"Missing boards.txt", "missing-boards.txt", assert.Nil, assert.NotNil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil, nil},
55+
{"Valid platform.txt", "valid-platform.txt", assert.NotNil, assert.Nil, assert.True, assert.NotNil, assert.Nil, assert.NotNil, []string{"foo_discovery", "bar_discovery"}, map[string][]string{"avrdude": {"foo_field_name"}}, []string{"avrdude", "bossac"}},
56+
{"Invalid platform.txt", "invalid-platform.txt", assert.NotNil, assert.Nil, assert.True, assert.Nil, assert.NotNil, assert.Nil, nil, nil, nil},
57+
{"Missing platform.txt", "missing-platform.txt", assert.NotNil, assert.Nil, assert.False, assert.Nil, assert.NotNil, assert.Nil, nil, nil, nil},
5658
}
5759

5860
for _, testTable := range testTables {
@@ -74,6 +76,7 @@ func TestInitializeForPlatform(t *testing.T) {
7476
testTable.platformTxtLoadErrorAssertion(t, PlatformTxtLoadError(), testTable.testName)
7577
testTable.platformTxtSchemaValidationResultAssertion(t, PlatformTxtSchemaValidationResult(), testTable.testName)
7678
assert.Equal(t, testTable.platformTxtPluggableDiscoveryNamesAssertion, PlatformTxtPluggableDiscoveryNames(), testTable.testName)
79+
assert.True(t, reflect.DeepEqual(testTable.platformTxtUserProvidedFieldNamesAssertion, PlatformTxtUserProvidedFieldNames()), testTable.testName)
7780
assert.Equal(t, testTable.platformTxtToolNamesAssertion, PlatformTxtToolNames(), testTable.testName)
7881
}
7982
}

Diff for: internal/project/projectdata/testdata/platforms/valid-platform.txt/platform.txt

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ recipe.size.regex=asdf
2525
recipe.size.regex.data=asdf
2626
pluggable_discovery.foo_discovery.pattern=asdf
2727
pluggable_discovery.bar_discovery.pattern=zxcv
28+
tools.avrdude.upload.field.foo_field_name=Some field label
29+
tools.avrdude.upload.field.foo_field_name.secret=true
2830
tools.avrdude.upload.params.verbose=-v
2931
tools.avrdude.upload.params.quiet=-q -q
3032
tools.avrdude.upload.pattern=asdf

0 commit comments

Comments
 (0)