Skip to content

Commit 48dd167

Browse files
authored
Target validation during github build (ExpressLRS#1888)
* Add target file validation to build * Fix broken targets in targets.json file! * Revert Axis change * Find targets with malformed firmware file name * Fix broken target (found by @jurgelenas) * Ensure TXs use TX firmware and RXs use RX firmware
1 parent a0efde3 commit 48dd167

3 files changed

Lines changed: 186 additions & 18 deletions

File tree

.github/workflows/build.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ jobs:
3939
cd src
4040
PLATFORMIO_BUILD_FLAGS="-DRegulatory_Domain_ISM_2400" pio test -e native
4141
42+
validation:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v2
47+
- name: Set up Python
48+
uses: actions/setup-python@v1
49+
- name: Validate target files
50+
run: |
51+
cd src
52+
python python/targets_validator.py
53+
4254
targets:
4355
runs-on: ubuntu-latest
4456
outputs:
@@ -127,7 +139,7 @@ jobs:
127139
continue-on-error: true
128140

129141
firmware:
130-
needs: build
142+
needs: [build, validation, test]
131143
runs-on: ubuntu-latest
132144
steps:
133145
- name: Checkout code
@@ -152,6 +164,7 @@ jobs:
152164
path: dist/**/*
153165

154166
installer:
167+
needs: [validation, test]
155168
strategy:
156169
fail-fast: false
157170
matrix:

src/hardware/targets.json

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@
571571
"layout_file": "Generic 2400 Whoop Rx and VTx.json",
572572
"upload_methods": ["uart", "wifi", "betaflight"],
573573
"platform": "esp32",
574-
"firmware": "Unified_ESP32_2400_TR"
574+
"firmware": "Unified_ESP32_2400_RX"
575575
},
576576
"true_diversity": {
577577
"product_name": "Generic ESP32 True Diversity PA 2.4Ghz RX",
@@ -755,13 +755,15 @@
755755
"hiyounger": {
756756
"name": "HiYOUNGER",
757757
"tx_2400": {
758-
"product_name": "HiYOUNGER 2.4GHz TX",
759-
"lua_name": "HiYOUNGER TX2G4",
760-
"layout_file": "BETAFPV 2400 Nano.json",
761-
"upload_methods": ["uart", "wifi"],
762-
"platform": "esp32",
763-
"firmware": "Unified_ESP32_2400_TX",
764-
"prior_target_name": "BETAFPV_2400_TX"
758+
"plain": {
759+
"product_name": "HiYOUNGER 2.4GHz TX",
760+
"lua_name": "HiYOUNGER TX2G4",
761+
"layout_file": "BETAFPV 2400 Nano.json",
762+
"upload_methods": ["uart", "wifi"],
763+
"platform": "esp32",
764+
"firmware": "Unified_ESP32_2400_TX",
765+
"prior_target_name": "BETAFPV_2400_TX"
766+
}
765767
},
766768
"rx_2400": {
767769
"rx24t": {
@@ -793,13 +795,15 @@
793795
}
794796
},
795797
"tx_900": {
796-
"product_name": "HiYOUNGER 900 TX",
797-
"lua_name": "HiYOUNGER 900 TX",
798-
"layout_file": "BETAFPV 900 Nano.json",
799-
"upload_methods": ["uart", "wifi"],
800-
"platform": "esp32",
801-
"firmware": "Unified_ESP32_900_TX",
802-
"prior_target_name": "BETAFPV_900_TX"
798+
"plain": {
799+
"product_name": "HiYOUNGER 900 TX",
800+
"lua_name": "HiYOUNGER 900 TX",
801+
"layout_file": "BETAFPV 900 Nano.json",
802+
"upload_methods": ["uart", "wifi"],
803+
"platform": "esp32",
804+
"firmware": "Unified_ESP32_900_TX",
805+
"prior_target_name": "BETAFPV_900_TX"
806+
}
803807
},
804808
"rx_900": {
805809
"plain": {
@@ -993,7 +997,7 @@
993997
"rx_900": {
994998
"mini": {
995999
"product_name": "Jumper 900 Mini RX",
996-
"upload_methods": ["betaflight"],
1000+
"upload_methods": ["stlink", "betaflight"],
9971001
"platform": "stm32",
9981002
"firmware": "Jumper_RX_R900MINI",
9991003
"stlink": {
@@ -1229,7 +1233,7 @@
12291233
"rp3": {
12301234
"product_name": "RadioMaster RP3 Diversity 2.4GHz RX",
12311235
"lua_name": "RadioMstr RP3",
1232-
"layout_file": "Generic 2400 PA Diversity.json",
1236+
"layout_file": "Generic 2400 Diversity PA.json",
12331237
"overlay": {
12341238
"radio_dcdc": true
12351239
},

src/python/targets_validator.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import os, sys
2+
import json
3+
import glob
4+
5+
hadError = False
6+
firmwares = set()
7+
8+
def error(msg):
9+
global hadError
10+
hadError = True
11+
print(msg)
12+
13+
def validate_stm32(vendor, type, devname, device):
14+
for method in device['upload_methods']:
15+
if method not in ['stlink', 'dfu', 'uart', 'wifi', 'betaflight']:
16+
error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
17+
if 'stlink' not in device['upload_methods']:
18+
error(f'STM32 based devices must always have "stlink" as an upload_method for target "{vendor}.{type}.{devname}"')
19+
if 'stlink' not in device:
20+
error(f'STM32 based devices must always have "stlink" attribute for target "{vendor}.{type}.{devname}"')
21+
else:
22+
stlink = device['stlink']
23+
if 'cpus' not in stlink:
24+
error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a list of valid "cpus"')
25+
if 'offset' not in stlink:
26+
error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "offset"')
27+
if 'bootloader' not in stlink:
28+
error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "bootloader"')
29+
# could check the existence of the bootloader file
30+
31+
def validate_esp(vendor, type, devname, device):
32+
if 'lua_name' not in device:
33+
error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" child element')
34+
if len(device['lua_name']) > 16:
35+
error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" of 16 characters or less')
36+
# validate layout_file
37+
if 'layout_file' not in device:
38+
error(f'device "{vendor}.{type}.{devname}" must have a "layout_file" child element')
39+
else:
40+
dir = 'hardware/' + ('RX/' if type.startswith('rx') else 'TX/')
41+
if not os.path.isfile(dir + device['layout_file']):
42+
layout_file = device['layout_file']
43+
error(f'File specified by layout_file "{layout_file}" in target "{vendor}.{type}.{devname}", does not exist')
44+
# could validate overlay
45+
46+
def validate_esp32(vendor, type, devname, device):
47+
for method in device['upload_methods']:
48+
if method not in ['uart', 'etx', 'wifi', 'betaflight']:
49+
error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
50+
validate_esp(vendor, type, devname, device)
51+
52+
def validate_esp8285(vendor, type, devname, device):
53+
for method in device['upload_methods']:
54+
if method not in ['uart', 'wifi', 'betaflight']:
55+
error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
56+
validate_esp(vendor, type, devname, device)
57+
58+
def validate_devices(vendor, type, devname, device):
59+
if devname != devname.lower():
60+
error(f'device tag "{devname}" should be lowercase')
61+
if 'product_name' not in device:
62+
error(f'device "{vendor}.{type}.{devname}" must have a "product_name" child element')
63+
if 'upload_methods' not in device:
64+
error(f'device "{vendor}.{type}.{devname}" must have a "upload_methods" child element')
65+
66+
if 'firmware' not in device:
67+
error(f'device "{vendor}.{type}.{devname}" must have a "firmware" child element')
68+
else:
69+
firmware = device['firmware']
70+
if firmware not in firmwares:
71+
error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}"')
72+
elif (firmware.endswith('_TX') and 'tx_' not in type):
73+
error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be a TX target firmware')
74+
elif (firmware.endswith('_RX') and 'rx_' not in type):
75+
error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be an RX target firmware')
76+
77+
if 'platform' not in device:
78+
error(f'device "{vendor}.{type}.{devname}" must have a "platform" child element')
79+
else:
80+
platform = device['platform']
81+
if platform == 'stm32':
82+
validate_stm32(vendor, type, devname, device)
83+
elif platform == 'esp32':
84+
validate_esp32(vendor, type, devname, device)
85+
elif platform == 'esp8285':
86+
validate_esp8285(vendor, type, devname, device)
87+
else:
88+
error(f'invalid platform "{platform}" in device "{vendor}.{type}.{devname}"')
89+
90+
if 'features' in device:
91+
for feature in device['features']:
92+
if feature not in ['buzzer', 'unlock-higher-power', 'fan', 'sbus-uart']:
93+
error(f'features must contain one or more of [\'buzzer\', \'unlock-higher-power\', \'fan\', \'sbus-uart\'], if present in target "{vendor}.{type}.{devname}"')
94+
95+
def validate_vendor(name, types):
96+
if name != name.lower():
97+
error(f'vendor tag "{vendor}" should be lowercase')
98+
99+
if 'name' not in types:
100+
error(f'vendor "{vendor}" must have a "name" child element')
101+
102+
for type in types:
103+
if type not in ['rx_2400', 'rx_900', 'tx_2400', 'tx_900', 'name']:
104+
error(f'invalid tag "{type}" in "{vendor}"')
105+
if type in ['rx_2400', 'rx_900', 'tx_2400', 'tx_900']:
106+
for device in types[type]:
107+
validate_devices(name, type, device, types[type][device])
108+
109+
if __name__ == '__main__':
110+
targets = {}
111+
with open('hardware/targets.json') as f:
112+
targets = json.load(f)
113+
114+
for inifile in glob.iglob('targets/*.ini'):
115+
with open(inifile) as ini:
116+
for line in ini:
117+
if line.startswith('[env:'):
118+
try:
119+
firmware_file = line[5:line.index('_via_')]
120+
firmwares.add(firmware_file)
121+
except ValueError:
122+
print(line)
123+
None
124+
125+
for vendor in targets:
126+
validate_vendor(vendor, targets[vendor])
127+
128+
for inifile in glob.iglob('targets/*.ini'):
129+
with open(inifile) as ini:
130+
for line in ini:
131+
if line.startswith('[env:'):
132+
target = line
133+
if line.startswith('board_config'):
134+
eq = line.find('=')
135+
board = line[eq+1:].strip()
136+
parts = board.split('.')
137+
if len(parts) != 3:
138+
error(f'{inifile}: board_config must have 3 parts')
139+
else:
140+
vendor = parts[0]
141+
type = parts[1]
142+
device = parts[2]
143+
if vendor not in targets:
144+
error(f'{inifile}: targets.json file does not contain vendor {vendor}')
145+
elif type not in targets[vendor]:
146+
error(f'{inifile}: targets.json "{vendor}" does not contain type {type}')
147+
elif device not in targets[vendor][type]:
148+
error(f'{inifile}: targets.json "{vendor}.{type}" does not contain device {device}')
149+
if hadError:
150+
sys.exit(1)
151+
sys.exit(0)

0 commit comments

Comments
 (0)