Skip to content

Commit 18d551c

Browse files
committed
Add check_library_properties()
#21
1 parent 0795c41 commit 18d551c

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ script:
124124

125125
- check_library_structure "${SKETCHBOOK_FOLDER}/libraries/CapacitiveSensor"
126126

127+
- check_library_properties "${SKETCHBOOK_FOLDER}/libraries/NewPing"
128+
127129
# Test library installed from .zip with rename
128130
# Test library installed from .zip with dot in the folder name
129131
# Test board from hardware package manually installed from compressed file download

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ Check sketches to ensure they have the correct structure.
117117
Check a library to ensure they have the correct structure. This will also run `check_sketch_structure` on all sketches bundled with the library.
118118
- Parameter: **libraryPath** - Path containing a library.
119119

120+
##### `check_library_properties searchPath'
121+
Check [library.properties](https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format) metadata files for errors.
122+
- Parameter: **searchPath** - Path containing library.properties files. The path will be searched recursively and all library.properties files found under it will be checked.
123+
120124
##### `build_sketch sketchPath boardID allowFail IDEversion`
121125
##### `build_sketch sketchPath boardID allowFail [IDEversionList]`
122126
##### `build_sketch sketchPath boardID allowFail startIDEversion endIDEversion`

arduino-ci-script.sh

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,195 @@ function check_sketch_structure()
13901390
}
13911391

13921392

1393+
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format
1394+
function check_library_properties()
1395+
{
1396+
local -r searchPath="$1"
1397+
1398+
# Replace backslashes with slashes
1399+
local -r searchPathWithSlashes="${searchPath//\\//}"
1400+
# Remove trailing slash
1401+
local -r normalizedSearchPath="${searchPathWithSlashes%/}"
1402+
1403+
# Check whether folder exists
1404+
if [[ ! -d "$normalizedSearchPath" ]]; then
1405+
echo "ERROR: Specified folder: $normalizedSearchPath doesn't exist."
1406+
return 1
1407+
fi
1408+
1409+
# find all folders that contain a library.properties file
1410+
find "$normalizedSearchPath" -type f -regex '.*/[lL][iI][bB][rR][aA][rR][yY]\.[pP][rR][oO][pP][eE][rR][tT][iI][eE][sS]' | while read -r libraryPropertiesPath; do
1411+
1412+
# Check for incorrect filename case
1413+
if [[ "${libraryPropertiesPath: -18}" != 'library.properties' ]]; then
1414+
echo "ERROR: $libraryPropertiesPath has incorrect filename case, which causes it to not be recognized on a filename case-sensitive OS such as Linux. It must be library.properties"
1415+
return 2
1416+
fi
1417+
1418+
# Get rid of the CRs
1419+
local libraryProperties
1420+
libraryProperties=$(tr "\r" "\n" <"$libraryPropertiesPath")
1421+
1422+
# Check that all required fields exist
1423+
if ! grep --quiet --regexp='^[[:space:]]*name[[:space:]]*=' <<<"$libraryProperties"; then
1424+
echo "ERROR: $libraryPropertiesPath is missing required name field."
1425+
return 3
1426+
fi
1427+
if ! grep --quiet --regexp='^[[:space:]]*version[[:space:]]*=' <<<"$libraryProperties"; then
1428+
echo "ERROR: $libraryPropertiesPath is missing required version field."
1429+
return 4
1430+
fi
1431+
if ! grep --quiet --regexp='^[[:space:]]*author[[:space:]]*=' <<<"$libraryProperties"; then
1432+
echo "ERROR: $libraryPropertiesPath is missing required author field."
1433+
return 5
1434+
fi
1435+
if ! grep --quiet --regexp='^[[:space:]]*maintainer[[:space:]]*=' <<<"$libraryProperties"; then
1436+
if grep --quiet --regexp='^[[:space:]]*email[[:space:]]*=' <<<"$libraryProperties"; then
1437+
echo "WARNING: Use of undocumented email field in $libraryPropertiesPath. It's recommended to use the maintainer field instead, per the Arduino Library Specification."
1438+
else
1439+
echo "ERROR: $libraryPropertiesPath is missing required maintainer field."
1440+
return 6
1441+
fi
1442+
fi
1443+
if ! grep --quiet --regexp='^[[:space:]]*sentence[[:space:]]*=' <<<"$libraryProperties"; then
1444+
echo "ERROR: $libraryPropertiesPath is missing required sentence field."
1445+
return 7
1446+
fi
1447+
if ! grep --quiet --regexp='^[[:space:]]*paragraph[[:space:]]*=' <<<"$libraryProperties"; then
1448+
echo "ERROR: $libraryPropertiesPath is missing required paragraph field."
1449+
return 8
1450+
fi
1451+
if ! grep --quiet --regexp='^[[:space:]]*category[[:space:]]*=' <<<"$libraryProperties"; then
1452+
echo "ERROR: $libraryPropertiesPath is missing category field. This results in an invalid category warning."
1453+
return 9
1454+
fi
1455+
if ! grep --quiet --regexp='^[[:space:]]*url[[:space:]]*=' <<<"$libraryProperties"; then
1456+
echo "ERROR: $libraryPropertiesPath is missing required url field."
1457+
return 10
1458+
fi
1459+
1460+
# Check for invalid lines (anything other than property, comment, or blank line)
1461+
if grep --quiet --invert-match --extended-regexp --regexp='=' --regexp='^[[:space:]]*(#|$)' <<<"$libraryProperties"; then
1462+
echo "ERROR: $libraryPropertiesPath contains an invalid line."
1463+
return 11
1464+
fi
1465+
1466+
# Check for characters in the name value disallowed by the Library Manager indexer
1467+
if
1468+
! grep --regexp='^[[:space:]]*name[[:space:]]*=' <<<"$libraryProperties" | while read -r nameLine; do
1469+
local validNameRegex='^[[:space:]]*name[[:space:]]*=[[:space:]]*[a-zA-Z0-9._ -]+[[:space:]]*$'
1470+
if ! [[ "$nameLine" =~ $validNameRegex ]]; then
1471+
echo "ERROR: ${libraryPropertiesPath}\'s name value uses characters not allowed by the Arduino Library Manager indexer. See: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format"
1472+
return 1
1473+
fi
1474+
done
1475+
then
1476+
return 12
1477+
fi
1478+
1479+
1480+
# Check for invalid version
1481+
if ! grep --quiet --extended-regexp --regexp='^[[:space:]]*version[[:space:]]*=[[:space:]]*(((([1-9][0-9]*)|0)\.){0,2})(([1-9][0-9]*)|0)(-(([a-zA-Z0-9-]*\.)*)([a-zA-Z0-9-]+))?(\+(([a-zA-Z0-9-]*\.)*)([a-zA-Z0-9-]+))?[[:space:]]*$' <<<"$libraryProperties"; then
1482+
echo "ERROR: $libraryPropertiesPath has an invalid version value. Follow the semver specification: https://semver.org/"
1483+
return 13
1484+
fi
1485+
1486+
# Check for repeat of sentence in paragraph
1487+
if
1488+
! grep --regexp='^[[:space:]]*sentence[[:space:]]*=' <<<"$libraryProperties" | while read -r sentenceLine; do
1489+
local sentenceLineFrontStripped="${sentenceLine#"${sentenceLine%%[![:space:]]*}"}"
1490+
local sentenceValueEquals=${sentenceLineFrontStripped#sentence}
1491+
local sentenceValueEqualsFrontStripped="${sentenceValueEquals#"${sentenceValueEquals%%[![:space:]]*}"}"
1492+
local sentenceValue=${sentenceValueEqualsFrontStripped#=}
1493+
local sentenceValueFrontStripped="${sentenceValue#"${sentenceValue%%[![:space:]]*}"}"
1494+
local sentenceValueStripped="${sentenceValueFrontStripped%"${sentenceValueFrontStripped##*[![:space:]]}"}"
1495+
local sentenceValueStrippedNoPunctuation=${sentenceValueStripped%%\.}
1496+
if [[ "$sentenceValueStrippedNoPunctuation" != "" ]]; then
1497+
grep --regexp='^[[:space:]]*paragraph[[:space:]]*=' <<<"$libraryProperties" | while read -r paragraphLine; do
1498+
local paragraphLineFrontStripped="${paragraphLine#"${paragraphLine%%[![:space:]]*}"}"
1499+
local paragraphValueEquals=${paragraphLineFrontStripped#sentence}
1500+
local paragraphValueEqualsFrontStripped="${paragraphValueEquals#"${paragraphValueEquals%%[![:space:]]*}"}"
1501+
local paragraphValue=${paragraphValueEqualsFrontStripped#=}
1502+
if [[ "$paragraphValue" == *"$sentenceValueStrippedNoPunctuation"* ]]; then
1503+
echo "ERROR: ${libraryPropertiesPath}\'s paragraph value repeats the sentence. These strings are displayed one after the other in Library Manager so there is no point in redundancy."
1504+
return 1
1505+
fi
1506+
done
1507+
fi
1508+
done
1509+
then
1510+
return 14
1511+
fi
1512+
1513+
1514+
# Check for invalid category
1515+
if ! grep --quiet --extended-regexp --regexp='^[[:space:]]*category[[:space:]]*=[[:space:]]*((Display)|(Communication)|(Signal Input/Output)|(Sensors)|(Device Control)|(Timing)|(Data Storage)|(Data Processing)|(Other))[[:space:]]*$' <<<"$libraryProperties"; then
1516+
if grep --quiet --regexp='^[[:space:]]*category[[:space:]]*=[[:space:]]*Uncategorized[[:space:]]*$' <<<"$libraryProperties"; then
1517+
echo "WARNING: category \'Uncategorized\' used in $libraryPropertiesPath is not recommended. Please chose an appropriate category from https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format"
1518+
else
1519+
echo "ERROR: $libraryPropertiesPath has an invalid category value. Please chose a valid category from https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification#libraryproperties-file-format"
1520+
return 15
1521+
fi
1522+
fi
1523+
1524+
# Check for missing scheme on url value
1525+
if ! grep --quiet --extended-regexp --regexp='^[[:space:]]*url[[:space:]]*=[[:space:]]*(http://)|(https://)' <<<"$libraryProperties"; then
1526+
echo "ERROR: ${libraryPropertiesPath}\'s url value is missing the scheme (e.g. https://). URL scheme must be specified for Library Manager's \"More info\" link to be clickable."
1527+
return 16
1528+
fi
1529+
1530+
# Check for dead url value
1531+
if
1532+
! grep --regexp='^[[:space:]]*url[[:space:]]*=' <<<"$libraryProperties" | while read -r urlLine; do
1533+
local urlLineWithoutSpaces=${urlLine//[[:space:]]/}
1534+
local urlValue=${urlLineWithoutSpaces//url=/}
1535+
local urlStatus
1536+
urlStatus=$(curl --location --output /dev/null --silent --head --write-out '%{http_code}' "$urlValue")
1537+
local errorStatusRegex='^[045]'
1538+
if [[ "$urlStatus" =~ $errorStatusRegex ]]; then
1539+
echo "ERROR: ${libraryPropertiesPath}\'s url value returned error status $urlStatus."
1540+
return 1
1541+
fi
1542+
done
1543+
then
1544+
return 17
1545+
fi
1546+
1547+
# Check for invalid architectures
1548+
if
1549+
! grep --regexp='^[[:space:]]*architectures[[:space:]]*=' <<<"$libraryProperties" | while read -r architecturesLine; do
1550+
local architecturesLineWithoutSpaces=${architecturesLine//[[:space:]]/}
1551+
local architecturesValue=${architecturesLineWithoutSpaces//architectures=/}
1552+
local validArchitecturesRegex='^((\*)|(avr)|(sam)|(samd)|(stm32f4)|(nrf52)|(i586)|(i686)|(arc32)|(win10)|(esp8266)|(esp32)|(ameba)|(arm)|(efm32)|(iot2000)|(msp430)|(navspark)|(nRF5)|(pic)|(pic32)|(RFduino)|(solox)|(stm32)|(stm)|(STM32)|(STM32F1)|(STM32F3)|(STM32F4)|(STM32F2)|(STM32L1)|(STM32L4))$'
1553+
# Split string on ,
1554+
IFS=','
1555+
# Disable globbing, otherwise it fails when one of the architecture values is *
1556+
set -o noglob
1557+
for architecture in $architecturesValue; do
1558+
if ! [[ "$architecture" =~ $validArchitecturesRegex ]]; then
1559+
echo "ERROR: ${libraryPropertiesPath}\'s architectures field contains an invalid architecture ${architecture}. Note: architecture values are case-sensitive."
1560+
return 1
1561+
fi
1562+
done
1563+
# Re-enable globbing
1564+
set +o noglob
1565+
# Set IFS back to default
1566+
unset IFS
1567+
done
1568+
then
1569+
return 18
1570+
fi
1571+
1572+
# Check for empty includes value
1573+
if grep --quiet --regexp='^[[:space:]]*includes[[:space:]]*=[[:space:]]*$' <<<"$libraryProperties"; then
1574+
echo "ERROR: ${libraryPropertiesPath}\'s includes value is empty. Either define the field or remove it."
1575+
return 19
1576+
fi
1577+
1578+
done
1579+
}
1580+
1581+
13931582
# Set default verbosity (must be called after the function definitions
13941583
set_script_verbosity 0
13951584

0 commit comments

Comments
 (0)