Skip to content

Commit e688ba0

Browse files
committed
Add JSON schema for boards.txt
This schema defines the required data structure of the boards.txt configuration file of Arduino boards platforms.
1 parent 4ab9fe4 commit e688ba0

File tree

41 files changed

+4515
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4515
-4
lines changed

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

+1,031
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://raw.githubusercontent.com/arduino/arduino-lint/main/etc/schemas/arduino-boards-txt-permissive-schema.json",
4+
"title": "Arduino boards.txt JSON permissive schema",
5+
"description": "boards.txt contains the boards definitions of Arduino platforms. See: https://arduino.github.io/arduino-cli/latest/platform-specification/#boardstxt",
6+
"$comment": "For information on the boards.txt format, see https://godoc.org/github.com/arduino/go-properties-orderedmap",
7+
"type": "object",
8+
"properties": {
9+
"menu": {
10+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/menu/permissive/object"
11+
}
12+
},
13+
"patternProperties": {
14+
"^([^m].*|m([^e].*)?|me([^n].*)?|men([^u].*)?|menu.+)$": {
15+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/boardID/permissive/object"
16+
}
17+
}
18+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://raw.githubusercontent.com/arduino/arduino-lint/main/etc/schemas/arduino-boards-txt-schema.json",
4+
"title": "Arduino boards.txt JSON schema",
5+
"description": "boards.txt contains the boards definitions of Arduino platforms. See: https://arduino.github.io/arduino-cli/latest/platform-specification/#boardstxt",
6+
"$comment": "For information on the boards.txt format, see https://godoc.org/github.com/arduino/go-properties-orderedmap",
7+
"type": "object",
8+
"properties": {
9+
"menu": {
10+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/menu/specification/object"
11+
}
12+
},
13+
"patternProperties": {
14+
"^([^m].*|m([^e].*)?|me([^n].*)?|men([^u].*)?|menu.+)$": {
15+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/boardID/specification/object"
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://raw.githubusercontent.com/arduino/arduino-lint/main/etc/schemas/arduino-boards-txt-strict-schema.json",
4+
"title": "Arduino boards.txt JSON strict schema",
5+
"description": "boards.txt contains the boards definitions of Arduino platforms. See: https://arduino.github.io/arduino-cli/latest/platform-specification/#boardstxt",
6+
"$comment": "For information on the boards.txt format, see https://godoc.org/github.com/arduino/go-properties-orderedmap",
7+
"type": "object",
8+
"properties": {
9+
"menu": {
10+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/menu/strict/object"
11+
}
12+
},
13+
"patternProperties": {
14+
"^([^m].*|m([^e].*)?|me([^n].*)?|men([^u].*)?|menu.+)$": {
15+
"$ref": "arduino-boards-txt-definitions-schema.json#/definitions/propertiesObjects/boardID/strict/object"
16+
}
17+
}
18+
}

internal/project/platform/boardstxt/boardstxt.go

+69-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,79 @@ See: https://arduino.github.io/arduino-cli/latest/platform-specification/#boards
2020
package boardstxt
2121

2222
import (
23+
"github.com/arduino/arduino-lint/internal/rule/schema"
24+
"github.com/arduino/arduino-lint/internal/rule/schema/compliancelevel"
25+
"github.com/arduino/arduino-lint/internal/rule/schema/schemadata"
2326
"github.com/arduino/go-paths-helper"
2427
"github.com/arduino/go-properties-orderedmap"
2528
)
2629

2730
// Properties parses the boards.txt from the given path and returns the data.
2831
func Properties(platformPath *paths.Path) (*properties.Map, error) {
29-
return properties.SafeLoadFromPath(platformPath.Join("boards.txt"))
32+
return properties.LoadFromPath(platformPath.Join("boards.txt"))
33+
}
34+
35+
var schemaObject = make(map[compliancelevel.Type]schema.Schema)
36+
37+
// Validate validates boards.txt data against the JSON schema and returns a map of the result for each compliance level.
38+
func Validate(boardsTxt *properties.Map) map[compliancelevel.Type]schema.ValidationResult {
39+
referencedSchemaFilenames := []string{
40+
"general-definitions-schema.json",
41+
"arduino-boards-txt-definitions-schema.json",
42+
}
43+
44+
var validationResults = make(map[compliancelevel.Type]schema.ValidationResult)
45+
46+
if schemaObject[compliancelevel.Permissive].Compiled == nil { // Only compile the schemas once.
47+
schemaObject[compliancelevel.Permissive] = schema.Compile("arduino-boards-txt-permissive-schema.json", referencedSchemaFilenames, schemadata.Asset)
48+
schemaObject[compliancelevel.Specification] = schema.Compile("arduino-boards-txt-schema.json", referencedSchemaFilenames, schemadata.Asset)
49+
schemaObject[compliancelevel.Strict] = schema.Compile("arduino-boards-txt-strict-schema.json", referencedSchemaFilenames, schemadata.Asset)
50+
}
51+
52+
/*
53+
Convert the boards.txt data from the native properties.Map type to the interface type required by the schema
54+
validation package.
55+
Even though boards.txt has a multi-level nested data structure, the format has the odd characteristic of allowing a
56+
key to be both an object and a string simultaneously, which is not compatible with Golang maps or JSON. So the data
57+
structure used is a map of the first level keys (necessary to accommodate the board IDs) to the full remainder of
58+
the keys (rather than recursing through the key levels individually), to string values.
59+
*/
60+
boardsTxtInterface := make(map[string]interface{})
61+
keys := boardsTxt.FirstLevelKeys()
62+
for _, key := range keys {
63+
subtreeMap := boardsTxt.SubTree(key).AsMap()
64+
// This level also must be converted to map[string]interface{}.
65+
subtreeInterface := make(map[string]interface{})
66+
for subtreeKey, subtreeValue := range subtreeMap {
67+
subtreeInterface[subtreeKey] = subtreeValue
68+
}
69+
boardsTxtInterface[key] = subtreeInterface
70+
}
71+
72+
validationResults[compliancelevel.Permissive] = schema.Validate(boardsTxtInterface, schemaObject[compliancelevel.Permissive])
73+
validationResults[compliancelevel.Specification] = schema.Validate(boardsTxtInterface, schemaObject[compliancelevel.Specification])
74+
validationResults[compliancelevel.Strict] = schema.Validate(boardsTxtInterface, schemaObject[compliancelevel.Strict])
75+
76+
return validationResults
77+
}
78+
79+
// MenuIDs returns the list of menu IDs from the given boards.txt properties.
80+
func MenuIDs(boardsTxt *properties.Map) []string {
81+
// Each menu must have a property defining its title with the format `menu.MENU_ID=MENU_TITLE`.
82+
return boardsTxt.SubTree("menu").FirstLevelKeys()
83+
}
84+
85+
// BoardIDs returns the list of board IDs from the given boards.txt properties.
86+
func BoardIDs(boardsTxt *properties.Map) []string {
87+
boardIDs := boardsTxt.FirstLevelKeys()
88+
boardIDCount := 0
89+
for _, boardID := range boardIDs {
90+
if boardID != "menu" {
91+
// This element is a board ID, retain it in the section of the array that will be returned.
92+
boardIDs[boardIDCount] = boardID
93+
boardIDCount++
94+
}
95+
}
96+
97+
return boardIDs[:boardIDCount]
3098
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// This file is part of arduino-lint.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-lint.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package boardstxt
17+
18+
import (
19+
"testing"
20+
21+
"github.com/arduino/arduino-lint/internal/rule/schema/compliancelevel"
22+
"github.com/arduino/go-paths-helper"
23+
"github.com/arduino/go-properties-orderedmap"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
var testDataPath *paths.Path
29+
30+
var validBoardsTxtMap map[string]string
31+
32+
func init() {
33+
workingDirectory, err := paths.Getwd()
34+
if err != nil {
35+
panic(err)
36+
}
37+
testDataPath = workingDirectory.Join("testdata")
38+
39+
validBoardsTxtMap = map[string]string{
40+
"uno.name": "Arduino Uno",
41+
"uno.build.board": "AVR_UNO",
42+
"uno.build.core": "arduino",
43+
"uno.upload.tool": "avrdude",
44+
"uno.upload.maximum_size": "123",
45+
"uno.upload.maximum_data_size": "123",
46+
}
47+
}
48+
49+
func TestProperties(t *testing.T) {
50+
propertiesOutput, err := Properties(testDataPath.Join("valid"))
51+
require.Nil(t, err)
52+
53+
assert.True(t, properties.NewFromHashmap(validBoardsTxtMap).Equals(propertiesOutput))
54+
}
55+
56+
func TestValidate(t *testing.T) {
57+
boardsTxt := properties.NewFromHashmap(validBoardsTxtMap)
58+
validationResult := Validate(boardsTxt)
59+
60+
assert.Nil(t, validationResult[compliancelevel.Permissive].Result)
61+
assert.Nil(t, validationResult[compliancelevel.Specification].Result)
62+
assert.Nil(t, validationResult[compliancelevel.Strict].Result)
63+
64+
boardsTxt.Remove("uno.name") // Remove required property.
65+
validationResult = Validate(boardsTxt)
66+
assert.NotNil(t, validationResult[compliancelevel.Permissive].Result)
67+
assert.NotNil(t, validationResult[compliancelevel.Specification].Result)
68+
assert.NotNil(t, validationResult[compliancelevel.Strict].Result)
69+
}
70+
71+
func TestMenuIDs(t *testing.T) {
72+
boardsTxt := properties.NewFromHashmap(validBoardsTxtMap)
73+
74+
assert.ElementsMatch(t, []string{}, MenuIDs(boardsTxt), "No menu IDs")
75+
76+
boardsTxt.Set("menu", "noooo")
77+
assert.ElementsMatch(t, []string{}, MenuIDs(boardsTxt), "Some silly defined a menu property without a subproperty")
78+
79+
boardsTxt.Set("menu.foo", "asdf")
80+
boardsTxt.Set("menu.bar", "zxcv")
81+
boardsTxt.Set("baz.name", "qwer")
82+
assert.ElementsMatch(t, []string{"foo", "bar"}, MenuIDs(boardsTxt), "Has menu IDs")
83+
}
84+
85+
func TestBoardIDs(t *testing.T) {
86+
boardsTxt := properties.NewFromHashmap(validBoardsTxtMap)
87+
88+
assert.ElementsMatch(t, []string{"uno"}, BoardIDs(boardsTxt))
89+
90+
boardsTxt.Set("menu.foo", "asdf")
91+
boardsTxt.Set("menu.bar", "zxcv")
92+
boardsTxt.Set("baz.name", "qwer")
93+
assert.ElementsMatch(t, []string{"uno", "baz"}, BoardIDs(boardsTxt))
94+
}

0 commit comments

Comments
 (0)