From 901258f16598ff772fe602e2d2ea2dd752c13b2b Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Fri, 13 Oct 2017 20:55:02 +0200 Subject: [PATCH] Implement Jenkins pipelines as code (#948) Jenkins has been updated with new jobs that scan and execute Jenkinsfiles. These Jenkinsfiles encapsulates all logic for building and validating the commits in both pull requests and merged pull requests. Filestructure is as follows: .ci/Jenkinsfile - This is the main Jenkinsfile that handles normal pull request builds as well as merged pull request post-validation. Any repository in the MySensors GitHub organization can implement this file and Jenkins will pick up and execute it automatically. .ci/Jenkinsfile-nightly - This perform merged pull request post-validation using the nightly builds of the Arduino IDE to provide early compliancy status of the MySensors library for upcoming releases of the Arduino IDE .ci/ - This folder contain all the auxiliary scripts executed by the Jenkinsfiles as well Fixes #934 --- .ci/Jenkinsfile | 18 ++ .ci/Jenkinsfile-nightly | 18 ++ .ci/arduino.groovy | 351 +++++++++++++++++++++++++++++++++++++ .ci/doxygen.groovy | 47 +++++ .ci/gitler.groovy | 105 +++++++++++ .ci/gitler.sh | 93 ++++++++++ .ci/linux.groovy | 71 ++++++++ .ci/pipeline.groovy | 196 +++++++++++++++++++++ .ci/pr-toolbox.groovy | 16 ++ .ci/static_analysis.groovy | 40 +++++ README.md | 6 +- hal/architecture/MyHw.h | 4 + 12 files changed, 962 insertions(+), 3 deletions(-) create mode 100644 .ci/Jenkinsfile create mode 100644 .ci/Jenkinsfile-nightly create mode 100644 .ci/arduino.groovy create mode 100644 .ci/doxygen.groovy create mode 100644 .ci/gitler.groovy create mode 100755 .ci/gitler.sh create mode 100644 .ci/linux.groovy create mode 100644 .ci/pipeline.groovy create mode 100644 .ci/pr-toolbox.groovy create mode 100644 .ci/static_analysis.groovy diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile new file mode 100644 index 00000000..a81f7c80 --- /dev/null +++ b/.ci/Jenkinsfile @@ -0,0 +1,18 @@ +#!groovy +node { + deleteDir() // Purge workspace + // Checkout the repo to get the necessary groovy scripts (and place it in the required subdirectory) + dir('MySensors') { + checkout scm + } + def pipeline = load('MySensors/.ci/pipeline.groovy') + + // Invoke the main pipeline + pipeline { + library_root = 'MySensors/' // Location of the MySensors library + repository_root = 'MySensors/' // Location of the repository root + github_organization = 'mysensors' // Name of the GitHub Organization + repository_name = 'MySensors' // Name of the repository on GitHub + nightly_arduino_ide = false // Pick Arduino IDE variant to use + } +} diff --git a/.ci/Jenkinsfile-nightly b/.ci/Jenkinsfile-nightly new file mode 100644 index 00000000..800bf18f --- /dev/null +++ b/.ci/Jenkinsfile-nightly @@ -0,0 +1,18 @@ +#!groovy +node { + deleteDir() // Purge workspace + // Checkout the repo to get the necessary groovy scripts (and place it in the required subdirectory) + dir('MySensors') { + checkout scm + } + def pipeline = load('MySensors/.ci/pipeline.groovy') + + // Invoke the main pipeline + pipeline { + library_root = 'MySensors/' // Location of the MySensors library + repository_root = 'MySensors/' // Location of the repository root + github_organization = 'mysensors' // Name of the GitHub Organization + repository_name = 'MySensors' // Name of the repository on GitHub + nightly_arduino_ide = true // Pick Arduino IDE variant to use + } +} diff --git a/.ci/arduino.groovy b/.ci/arduino.groovy new file mode 100644 index 00000000..9e7eaf18 --- /dev/null +++ b/.ci/arduino.groovy @@ -0,0 +1,351 @@ +#!groovy +def buildArduino(config, String buildFlags, String sketch, String key) { + def root = '/opt/arduino-1.8.2/' + if (config.nightly_arduino_ide) + { + root = '/opt/arduino-nightly/' + } + def esp8266_tools = '/opt/arduino-nightly/hardware/esp8266com/esp8266/tools' + def jenkins_root = '/var/lib/jenkins/' + def builder = root+'arduino-builder' + def standard_args = ' -warnings="all"' //-verbose=true + def builder_specifics = ' -hardware '+root+'hardware -tools '+root+'hardware/tools/avr -tools '+ + root+'tools-builder -tools '+esp8266_tools+' -built-in-libraries '+root+'libraries' + def jenkins_packages = jenkins_root+'.arduino15/packages' + def site_specifics = ' -hardware '+jenkins_packages+' -tools '+jenkins_packages + def repo_specifics = ' -hardware hardware -libraries . ' + def build_cmd = builder+standard_args+builder_specifics+site_specifics+repo_specifics+buildFlags + sh """#!/bin/bash + printf "\\e[1m\\e[32mBuilding \\e[34m${sketch} \\e[0musing \\e[1m\\e[36m${build_cmd}\\e[0m\\n" + ${build_cmd} ${sketch} 2>> compiler_${key}.log""" +} + +def parseWarnings(String key) { + warnings canResolveRelativePaths: false, canRunOnFailed: true, categoriesPattern: '', + defaultEncoding: '', + excludePattern: '''.*/EEPROM\\.h,.*/Dns\\.cpp,.*/socket\\.cpp,.*/util\\.h,.*/Servo\\.cpp, + .*/Adafruit_NeoPixel\\.cpp,.*/UIPEthernet.*,.*/SoftwareSerial\\.cpp, + .*/pins_arduino\\.h,.*/Stream\\.cpp,.*/USBCore\\.cpp,.*/Wire\\.cpp, + .*/hardware/esp8266.*,.*/libraries/SD/.*''', + healthy: '', includePattern: '', messagesPattern: '', + parserConfigurations: [[parserName: 'Arduino/AVR', pattern: 'compiler_'+key+'.log']], + unHealthy: '', unstableNewAll: '0', unstableTotalAll: '0' + sh """#!/bin/bash + echo "Compiler warnings/errors:" + printf "\\e[101m" + cat compiler_${key}.log + printf "\\e[0m" + rm compiler_${key}.log""" +} + +def buildMySensorsMicro(config, sketches, String key) { + def fqbn = '-fqbn MySensors:avr:MysensorsMicro -prefs build.f_cpu=1000000 -prefs build.mcu=atmega328p' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (MySensorsMicro - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_MySensorsMicro') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (MySensorsMicro - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_MySensorsMicro') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (MySensorsMicro - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (MySensorsMicro - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (MySensorsMicro - '+key+')', 'Pass', '') + } +} + +def buildMySensorsGw(config, sketches, String key) { + def fqbn = '-fqbn MySensors:samd:mysensors_gw_native -prefs build.f_cpu=48000000 -prefs build.mcu=cortex-m0plus' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (MySensorsGW - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/BatteryPoweredSensor/BatteryPoweredSensor.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' && + sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_MySensorsGw') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (MySensorsGW - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_MySensorsGw') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (MySensorsGW - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (MySensorsGW - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (MySensorsGW - '+key+')', 'Pass', '') + } +} + +def buildArduinoNano(config, sketches, String key) { + def fqbn = '-fqbn arduino:avr:nano -prefs build.f_cpu=16000000 -prefs build.mcu=atmega328p' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Arduino Nano - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_ArduinoNano') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Nano - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_ArduinoNano') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Arduino Nano - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Nano - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Arduino Nano - '+key+')', 'Pass', '') + } +} + +def buildArduinoUno(config, sketches, String key) { + def fqbn = '-fqbn arduino:avr:uno -prefs build.f_cpu=16000000 -prefs build.mcu=atmega328p' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Arduino Uno - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_ArduinoUno') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Uno - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_ArduinoUno') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Arduino Uno - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Uno - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Arduino Uno - '+key+')', 'Pass', '') + } +} + +def buildArduinoPro(config, sketches, String key) { + def fqbn = '-fqbn arduino:avr:pro -prefs build.f_cpu=8000000 -prefs build.mcu=atmega328p' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Arduino Pro/Mini - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_ArduinoPro') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Pro/Mini - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_ArduinoPro') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, config, 'ERROR', 'Toll gate (Arduino Pro/Mini - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Pro/Mini - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Arduino Pro/Mini - '+key+')', 'Pass', '') + } +} + +def buildArduinoMega(config, sketches, String key) { + def fqbn = '-fqbn arduino:avr:mega -prefs build.f_cpu=16000000 -prefs build.mcu=atmega2560' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Arduino Mega - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_ArduinoMega') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Mega - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_ArduinoMega') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, config, 'ERROR', 'Toll gate (Arduino Mega - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Arduino Mega - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Arduino Mega - '+key+')', 'Pass', '') + } +} + +def buildEsp8266(config, sketches, String key) { + def fqbn = '-fqbn esp8266:esp8266:generic -prefs build.f_cpu=80000000 -prefs build.mcu=esp8266' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (ESP8266 - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/BatteryPoweredSensor/BatteryPoweredSensor.ino' && + sketches[sketch].path != config.library_root+'examples/CO2Sensor/CO2Sensor.ino' && + sketches[sketch].path != config.library_root+'examples/DustSensorDSM/DustSensorDSM.ino' && + sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayW5100/GatewayW5100.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayW5100MQTTClient/GatewayW5100MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino' && + sketches[sketch].path != config.library_root+'examples/SoilMoistSensor/SoilMoistSensor.ino') { + buildArduino(config, '-prefs build.flash_ld=eagle.flash.512k0.ld -prefs build.flash_freq=40 -prefs build.flash_size=512K '+fqbn, sketches[sketch].path, key+'_Esp8266') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (ESP8266 - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_Esp8266') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (ESP8266 - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (ESP8266 - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (ESP8266 - '+key+')', 'Pass', '') + } +} + +def buildnRF5(config, sketches, String key) { + def fqbn = '-fqbn sandeepmistry:nRF5:Generic_nRF52832 -prefs build.f_cpu=16000000 -prefs build.mcu=cortex-m4' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (nRF5 - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + for (sketch = 0; sketch < sketches.size(); sketch++) { + if (sketches[sketch].path != config.library_root+'examples/BatteryPoweredSensor/BatteryPoweredSensor.ino' && + sketches[sketch].path != config.library_root+'examples/CO2Sensor/CO2Sensor.ino' && + sketches[sketch].path != config.library_root+'examples/DustSensorDSM/DustSensorDSM.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266/GatewayESP8266.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266MQTTClient/GatewayESP8266MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayESP8266OTA/GatewayESP8266OTA.ino' && + sketches[sketch].path != config.library_root+'examples/GatewaySerialRS485/GatewaySerialRS485.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayW5100/GatewayW5100.ino' && + sketches[sketch].path != config.library_root+'examples/GatewayW5100MQTTClient/GatewayW5100MQTTClient.ino' && + sketches[sketch].path != config.library_root+'examples/MotionSensorRS485/MotionSensorRS485.ino' && + sketches[sketch].path != config.library_root+'examples/SensebenderGatewaySerial/SensebenderGatewaySerial.ino') { + buildArduino(config, fqbn, sketches[sketch].path, key+'_nRF5') + } + } + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF5 - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_nRF5') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (nRF5 - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF5 - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (nRF5 - '+key+')', 'Pass', '') + } +} + +def buildnRF52832(config, sketches, String key) { + def fqbn = '-fqbn=MySensors:nRF5:MyBoard_nRF52832:bootcode=none,lfclk=lfxo,reset=notenable -prefs build.f_cpu=16000000 -prefs build.mcu=cortex-m4' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (nRF52832 - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + buildArduino(config, fqbn, 'hardware/MySensors/nRF5/libraries/MyNRF5Board/examples/MyNRF5Board/MyNRF5Board.ino', key+'_nRF52832') + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF52832 - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_nRF52832') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (nRF52832 - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF52832 - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (nRF52832 - '+key+')', 'Pass', '') + } +} + +def buildnRF51822(config, sketches, String key) { + def fqbn = '-fqbn=MySensors:nRF5:MyBoard_nRF51822:chip=xxaa,bootcode=none,lfclk=lfxo -prefs build.f_cpu=16000000 -prefs build.mcu=cortex-m0' + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (nRF51822 - '+key+')', 'Building...', '${BUILD_URL}flowGraphTable/') + try { + buildArduino(config, fqbn, 'hardware/MySensors/nRF5/libraries/MyNRF5Board/examples/MyNRF5Board/MyNRF5Board.ino', key+'_nRF51822') + } catch (ex) { + echo "Build failed with: "+ ex.toString() + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF51822 - '+key+')', 'Build error', '${BUILD_URL}') + throw ex + } finally { + parseWarnings(key+'_nRF51822') + } + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (nRF51822 - '+key+')', 'Warnings found', '${BUILD_URL}warnings2Result/new') + if (config.is_pull_request) { + error 'Termiated due to warnings found' + } + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (nRF51822 - '+key+')', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (nRF51822 - '+key+')', 'Pass', '') + } +} + +return this \ No newline at end of file diff --git a/.ci/doxygen.groovy b/.ci/doxygen.groovy new file mode 100644 index 00000000..00247f79 --- /dev/null +++ b/.ci/doxygen.groovy @@ -0,0 +1,47 @@ +#!groovy +def call(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Documentation)', 'Generating...', '${BUILD_URL}flowGraphTable/') + sh """#!/bin/bash +x + cd ${config.repository_root} + export PROJECTNUMBER=\$( + if [[ \$(git rev-parse --abbrev-ref HEAD) == "master" ]]; then + git describe --tags ; + else + git rev-parse --short HEAD ; + fi + ) + echo 'WARN_LOGFILE=doxygen.log' >> Doxyfile && doxygen""" + warnings canComputeNew: false, canResolveRelativePaths: false, + defaultEncoding: '', + excludePattern: '''.*/sha204_library\\.h,.*/drivers/Linux/.*,.*/cores/esp8266/.*,hardware/.*''', + failedTotalAll: '', healthy: '', includePattern: '', messagesPattern: '', + parserConfigurations: [[parserName: 'Doxygen', pattern: config.repository_root+'doxygen.log']], + unHealthy: '', unstableTotalAll: '0' + publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, + reportDir: config.repository_root+'Documentation/html', + reportFiles: 'index.html', reportName: 'Doxygen HTML', reportTitles: '']) + + if (!config.is_pull_request) + { + // Publish docs to API server + if (env.BRANCH_NAME == 'master') { + sh """#!/bin/bash + scp -r ${config.repository_root}Documentation/html docs@direct.openhardware.io""" + } else if (env.BRANCH_NAME == 'development') { + sh """#!/bin/bash + scp -r ${config.repository_root}Documentation/html docs@direct.openhardware.io:beta""" + } + } else { + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Documentation)', 'Warnings found', '${BUILD_URL}warnings16Result/new') + error 'Terminating due to doxygen error' + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Documentation)', 'Error generating documentation', '${BUILD_URL}flowGraphTable/') + error 'Terminating due to doxygen error' + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Documentation)', 'Pass', '${BUILD_URL}Doxygen_HTML/index.html') + } + } +} + +return this \ No newline at end of file diff --git a/.ci/gitler.groovy b/.ci/gitler.groovy new file mode 100644 index 00000000..6def1703 --- /dev/null +++ b/.ci/gitler.groovy @@ -0,0 +1,105 @@ +#!groovy +def call(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Gitler)', 'Checking...', '${BUILD_URL}flowGraphTable/') + if (env.CHANGE_TARGET == 'master' && + (env.CHANGE_AUTHOR != 'bblacey' || env.CHANGE_AUTHOR != 'd00616' || + env.CHANGE_AUTHOR != 'fallberg' || env.CHANGE_AUTHOR != 'henrikekblad' || + env.CHANGE_AUTHOR != 'marceloaqno' || env.CHANGE_AUTHOR != 'mfalkvidd' || + env.CHANGE_AUTHOR != 'scalz' || env.CHANGE_AUTHOR != 'tbowmo' || + env.CHANGE_AUTHOR != 'tekka007' || env.CHANGE_AUTHOR != 'user2684' || + env.CHANGE_AUTHOR != 'Yveaux')) + { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Gitler)', 'This pull request targets master. That is not permitted!', '') + error "This pull request targets master. That is not permitted!" + } + + dir(config.repository_root) { + step([$class: 'GitChangelogRecorder', config: [configFile: 'git-changelog-settings.json', + createFileTemplateContent: ''' +{{#commits}} +{{{messageTitle}}} +{{/commits}} +''', + createFileTemplateFile: '', createFileUseTemplateContent: true, + createFileUseTemplateFile: false, customIssues: [[link: '', name: '', pattern: '', title: ''], + [link: '', name: '', pattern: '', title: '']], dateFormat: 'YYYY-MM-dd HH:mm:ss', + file: 'subjects.txt', fromReference: env.CHANGE_TARGET, fromType: 'ref', gitHubApi: '', + gitHubApiTokenCredentialsId: '', gitHubIssuePattern: '#([0-9]+)', gitHubToken: '', + gitLabApiTokenCredentialsId: '', gitLabProjectName: '', gitLabServer: '', gitLabToken: '', + ignoreCommitsIfMessageMatches: '^Merge.*', + ignoreCommitsWithoutIssue: false, ignoreTagsIfNameMatches: '', + jiraIssuePattern: '\\b[a-zA-Z]([a-zA-Z]+)-([0-9]+)\\b', jiraPassword: '', jiraServer: '', + jiraUsername: '', jiraUsernamePasswordCredentialsId: '', mediaWikiPassword: '', + mediaWikiTemplateContent: '', mediaWikiTemplateFile: '', mediaWikiTitle: '', mediaWikiUrl: '', + mediaWikiUseTemplateContent: false, mediaWikiUseTemplateFile: false, mediaWikiUsername: '', + noIssueName: 'No issue', readableTagName: '/([^/]+?)$', showSummary: false, + showSummaryTemplateContent: '', showSummaryTemplateFile: '', showSummaryUseTemplateContent: false, + showSummaryUseTemplateFile: false, subDirectory: '', timeZone: 'UTC', + toReference: config.git_sha, toType: 'commit', untaggedName: 'Unreleased', + useConfigFile: false, useFile: true, useGitHub: false, useGitHubApiTokenCredentials: false, + useGitLab: false, useGitLabApiTokenCredentials: false, useIgnoreTagsIfNameMatches: false, + useJira: false, useJiraUsernamePasswordCredentialsId: false, useMediaWiki: false, + useReadableTagName: false, useSubDirectory: false] + ]) + step([$class: 'GitChangelogRecorder', config: [configFile: 'git-changelog-settings.json', + createFileTemplateContent: ''' +{{#commits}} +{{#messageBodyItems}} +{{.}} +{{/messageBodyItems}} +{{/commits}} +''', + createFileTemplateFile: '', createFileUseTemplateContent: true, + createFileUseTemplateFile: false, customIssues: [[link: '', name: '', pattern: '', title: ''], + [link: '', name: '', pattern: '', title: '']], dateFormat: 'YYYY-MM-dd HH:mm:ss', + file: 'bodies.txt', fromReference: env.CHANGE_TARGET, fromType: 'ref', gitHubApi: '', + gitHubApiTokenCredentialsId: '', gitHubIssuePattern: '#([0-9]+)', gitHubToken: '', + gitLabApiTokenCredentialsId: '', gitLabProjectName: '', gitLabServer: '', gitLabToken: '', + ignoreCommitsIfMessageMatches: '^Merge.*', + ignoreCommitsWithoutIssue: false, ignoreTagsIfNameMatches: '', + jiraIssuePattern: '\\b[a-zA-Z]([a-zA-Z]+)-([0-9]+)\\b', jiraPassword: '', jiraServer: '', + jiraUsername: '', jiraUsernamePasswordCredentialsId: '', mediaWikiPassword: '', + mediaWikiTemplateContent: '', mediaWikiTemplateFile: '', mediaWikiTitle: '', mediaWikiUrl: '', + mediaWikiUseTemplateContent: false, mediaWikiUseTemplateFile: false, mediaWikiUsername: '', + noIssueName: 'No issue', readableTagName: '/([^/]+?)$', showSummary: false, + showSummaryTemplateContent: '', showSummaryTemplateFile: '', showSummaryUseTemplateContent: false, + showSummaryUseTemplateFile: false, subDirectory: '', timeZone: 'UTC', + toReference: config.git_sha, toType: 'commit', untaggedName: 'Unreleased', + useConfigFile: false, useFile: true, useGitHub: false, useGitHubApiTokenCredentials: false, + useGitLab: false, useGitLabApiTokenCredentials: false, useIgnoreTagsIfNameMatches: false, + useJira: false, useJiraUsernamePasswordCredentialsId: false, useMediaWiki: false, + useReadableTagName: false, useSubDirectory: false] + ]) + } + + ret = sh(returnStatus: true, + script:"""#!/bin/bash + cd ${config.repository_root}/.ci + ./gitler.sh""") + + if (fileExists(config.repository_root+'restyling.patch')) { + emailext ( + subject: "Job '${env.JOB_NAME} #${env.BUILD_NUMBER} [PR#${env.CHANGE_ID}]' failed due to bad code styling", + body: """

Job '${env.JOB_NAME} [PR#${env.CHANGE_ID} - ${env.CHANGE_TITLE}]' failed because code style does not follow the standards.

+ A patch to rectify the errors is attached. You apply the patch using:
+ git apply restyling.patch

+ If you disagree to this, please discuss it here.

+ Yours sincerely, Gitler, on behalf of Jenkins""", + mimeType: 'text/html', to: '${env.CHANGE_AUTHOR_EMAIL}', + attachLog: false, compressLog: false, attachmentsPattern: config.repository_root+'restyling.patch' + ) + } + publishHTML([allowMissing: true, alwaysLinkToLastBuild: false, keepAll: true, + reportDir: config.repository_root, + reportFiles: 'gitler.html', reportName: 'Gitler report', reportTitles: '']) + if (ret == 1) { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Gitler)', 'Commit(s) does not meet coding standards', '${BUILD_URL}Gitler_report/gitler.html') + currentBuild.currentResult == 'FAILURE' + echo "Termiated due to Gitler assert" // For BFA + error 'Termiated due to Gitler assert' + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Gitler)', 'Pass', '') + } +} + +return this \ No newline at end of file diff --git a/.ci/gitler.sh b/.ci/gitler.sh new file mode 100755 index 00000000..5cb3b6b0 --- /dev/null +++ b/.ci/gitler.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Yes, this script can be improved immensly... +cd .. + +result=0 + +echo "No subjects are of invalid size
" > too_long_subjects.txt +echo "No subjects with leading lower case characters
" > leading_lowercases.txt +echo "No subjects with trailing periods
" > trailing_periods.txt +echo "No body lines are too wide
" > too_long_body_lines.txt + +too_long_subjects=`awk 'length > 50' subjects.txt` +if [ -n "$too_long_subjects" ]; then + echo "Commit subjects that are too wide (>50 characters):" > too_long_subjects.txt + echo "$too_long_subjects" >> too_long_subjects.txt + sed -i -e 's/$/
/' too_long_subjects.txt + result=1 +fi +leading_lowercases=`awk '/^[[:lower:][:punct:]]/' subjects.txt` +if [ -n "$leading_lowercases" ]; then + echo "Commit subjects with leading lowercase characters:" > leading_lowercases.txt + echo "$leading_lowercases" >> leading_lowercases.txt + sed -i -e 's/$/
/' leading_lowercases.txt + result=1 +fi +trailing_periods=`awk '/(\.)$/' subjects.txt` +if [ -n "$trailing_periods" ]; then + echo "Commit subjects with trailing periods:" > trailing_periods.txt + echo "$trailing_periods" >> trailing_periods.txt + sed -i -e 's/$/
/' trailing_periods.txt + result=1 +fi + +too_long_body_lines=`awk 'length > 72' bodies.txt` +if [ -n "$too_long_body_lines" ]; then + echo "Body lines that are too wide (>72 characters):" > too_long_body_lines.txt + echo "$too_long_body_lines" >> too_long_body_lines.txt + sed -i -e 's/$/
/' too_long_body_lines.txt + result=1 +fi + +printf "%s" "" > gitler.html +awk 'FNR==1{print "
"}1' too_long_subjects.txt leading_lowercases.txt trailing_periods.txt too_long_body_lines.txt >> gitler.html +echo "
" >> gitler.html +if [ $result -ne 0 ]; then + echo "You should follow this guide when writing your commit messages.
" >> gitler.html + echo "
" >> gitler.html + echo "To change the commit message for a single-commit pull request:
" >> gitler.html + echo "git checkout <your_branch>
" >> gitler.html + echo "git commit --amend
" >> gitler.html + echo "git push origin HEAD:<your_branch_on_github> -f
" >> gitler.html + echo "
" >> gitler.html + echo "To change the commit messages for a multiple-commit pull request:
" >> gitler.html + echo "git checkout <your_branch>
" >> gitler.html + echo "git rebase -i <sha_of_parent_to_the_earliest_commit_you_want_to_change>
" >> gitler.html + echo "Replace \"pick\" with \"r\" or \"reword\" on the commits you need to change message for
" >> gitler.html + echo "git push origin HEAD:<your_branch_on_github> -f
" >> gitler.html + echo "
" >> gitler.html +fi + +# Evaluate coding style +astyle --options=.mystools/astyle/config/style.cfg -nq --recursive "*.h" "*.c" "*.cpp" +git diff > restyling.patch +if [ -s restyling.patch ]; then + echo "This commit is not meeting the coding standards, a mail with a patch has been sent to you that if applied to your PR, will make it meet the coding standards.
" >> gitler.html + echo "You apply the patch using:
" >> gitler.html + echo "git apply restyling.patch
" >> gitler.html + echo "
" >> gitler.html + result=1 +else + echo "This commit is meeting the coding standards, congratulations!
" >> gitler.html + echo "
" >> gitler.html + rm restyling.patch +fi + +# Evaluate if there exists booleans in the code tree (not counting this file) +if git grep -q boolean -- `git ls-files | grep -v gitler.sh` +then + echo "This repository currently contain the boolean keyword. This should be avoided.
" >> gitler.html + echo "
" >> gitler.html + result=1 +else + echo "This repository does not contain any boolean keywords. This is good.
" >> gitler.html + echo "
" >> gitler.html +fi + +if [ $result -ne 0 ]; then + echo "If you disagree to this, please discuss it in the GitHub pull request thread.
" >> gitler.html + echo "
" >> gitler.html +fi +echo "Yours sincerely, Gitler, on behalf of Jenkins
" >> gitler.html +printf "%s" "" >> gitler.html +exit $result diff --git a/.ci/linux.groovy b/.ci/linux.groovy new file mode 100644 index 00000000..672a530b --- /dev/null +++ b/.ci/linux.groovy @@ -0,0 +1,71 @@ +#!groovy +def buildLinux(config, String configuration, String key) { + def linux_configurer = './configure ' + def linux_configure_standard_args = '--my-rs485-serial-port=/dev/ttyS0 --my-controller-ip-address=1.2.3.4 '+ + '--my-mqtt-subscribe-topic-prefix=dummy --my-mqtt-publish-topic-prefix==dummy --my-mqtt-client-id=0 ' + def linux_builder = 'make ' + def linux_builder_standard_args = '-j1' + def linux_build_cmd = linux_builder + linux_builder_standard_args + def linux_configure_cmd = linux_configurer + linux_configure_standard_args + configuration + sh """#!/bin/bash + printf "\\e[1m\\e[32mBuilding \\e[0musing \\e[1m\\e[36m${linux_build_cmd} \\e[0mwith \\e[1m\\e[34m${linux_configure_standard_args} ${configuration}\\e[0m\\n" + cd ${config.library_root} + ${linux_configure_cmd} + ${linux_build_cmd} 2>> compiler_${key}.log""" + warnings canComputeNew: false, canResolveRelativePaths: false, + defaultEncoding: '', + excludePattern: '''.*/EEPROM\\.h,.*/Dns\\.cpp,.*/socket\\.cpp,.*/util\\.h,.*/Servo\\.cpp, + .*/Adafruit_NeoPixel\\.cpp,.*/UIPEthernet.*,.*/SoftwareSerial\\.cpp, + .*/pins_arduino\\.h,.*/Stream\\.cpp,.*/USBCore\\.cpp,.*/Wire\\.cpp, + .*/hardware/esp8266.*,.*/libraries/SD/.*''', + failedTotalAll: '', healthy: '', includePattern: '', messagesPattern: '', + parserConfigurations: [[parserName: 'GNU Make + GNU C Compiler (gcc)', pattern: config.library_root+'compiler_'+key+'.log']], + unHealthy: '', unstableTotalAll: '0' + sh """#!/bin/bash + echo "Compiler warnings/errors:" + printf "\\e[101m" + cat ${config.library_root}compiler_${key}.log + printf "\\e[0m" + rm ${config.library_root}compiler_${key}.log""" +} + +def buildSerial(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Linux builds - Serial GW)', 'Building...', '${BUILD_URL}flowGraphTable/') + buildLinux(config, '--my-debug=disable --my-transport=none --my-gateway=serial', 'Serial') + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Linux builds - Serial GW)', 'Warnings found', '${BUILD_URL}warnings28Result/new') + error 'Termiated due to warnings found' + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Linux builds - Serial GW)', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Linux builds - Serial GW)', 'Pass', '') + } +} + +def buildEthernet(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Linux builds - Ethernet GW)', 'Building...', '${BUILD_URL}flowGraphTable/') + buildLinux(config, '--my-debug=enable --my-transport=rs485 --my-gateway=ethernet', 'Ethernet') + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Linux builds - Ethernet GW)', 'Warnings found', '${BUILD_URL}warnings28Result/new') + error 'Termiated due to warnings found' + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Linux builds - Ethernet GW)', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Linux builds - Ethernet GW)', 'Pass', '') + } +} + +def buildMQTT(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Linux builds - MQTT GW)', 'Building...', '${BUILD_URL}flowGraphTable/') + buildLinux(config, '--my-debug=disable --my-transport=none --my-gateway=mqtt', 'MQTT') + if (currentBuild.currentResult == 'UNSTABLE') { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate (Linux builds - MQTT GW)', 'Warnings found', '${BUILD_URL}warnings28Result/new') + error 'Termiated due to warnings found' + } else if (currentBuild.currentResult == 'FAILURE') { + config.pr.setBuildStatus(config, 'FAILURE', 'Toll gate (Linux builds - MQTT GW)', 'Build error', '${BUILD_URL}') + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Linux builds - MQTT GW)', 'Pass', '') + } +} + +return this \ No newline at end of file diff --git a/.ci/pipeline.groovy b/.ci/pipeline.groovy new file mode 100644 index 00000000..f3c244b5 --- /dev/null +++ b/.ci/pipeline.groovy @@ -0,0 +1,196 @@ +#!groovy + +def call(Closure body) { + def config = [:] + body.resolveStrategy = Closure.DELEGATE_FIRST + body.delegate = config + body() + + config.pr = load(config.repository_root+'.ci/pr-toolbox.groovy') + def linux = load(config.repository_root+'.ci/linux.groovy') + def arduino = load(config.repository_root+'.ci/arduino.groovy') + + if (env.CHANGE_ID) { + config.is_pull_request = true + echo "Building pull request: #"+env.CHANGE_ID+"\nTarget branch: "+env.CHANGE_TARGET + config.git_sha = sh(returnStdout: true, + script: """#!/bin/bash + cd ${config.repository_root} + git log -n 1 --pretty=format:'%H' refs/remotes/origin/PR-${env.CHANGE_ID}""").trim() + } else { + config.is_pull_request = false + echo "Building branch: "+env.BRANCH_NAME + config.git_sha = sh(returnStdout: true, + script: """#!/bin/bash + cd ${config.repository_root} + git log -n 1 --pretty=format:'%H' refs/remotes/origin/${env.BRANCH_NAME}""").trim() + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate', 'Validating...', '${BUILD_URL}flowGraphTable/') + } + + try { + ansiColor('xterm') { + if (config.is_pull_request) { + def gitler = load(config.repository_root+'.ci/gitler.groovy') + stage('Gitler') { + gitler(config) + } + } + + stage('Preparation') { + checkout(changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], + extensions: [ + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: true], + [$class: 'RelativeTargetDirectory', relativeTargetDir: 'hardware/MySensors/avr'] + ], + userRemoteConfigs: [[url: 'https://github.com/mysensors/ArduinoHwAVR.git']]]) + checkout(changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], + extensions: [ + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: true], + [$class: 'RelativeTargetDirectory', relativeTargetDir: 'hardware/MySensors/samd'] + ], + userRemoteConfigs: [[url: 'https://github.com/mysensors/ArduinoHwSAMD.git']]]) + checkout(changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], + extensions: [ + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: true], + [$class: 'RelativeTargetDirectory', relativeTargetDir: 'hardware/MySensors/nRF5'] + ], + userRemoteConfigs: [[url: 'https://github.com/mysensors/ArduinoHwNRF5.git']]]) + + config.tests = findFiles(glob: config.library_root+'tests/**/*.ino') + config.examples = findFiles(glob: config.library_root+'examples/**/*.ino') + + } + + parallel Doxygen: { + if (!config.nightly_arduino_ide) { + stage('Doxygen') { + def doxygen = load(config.repository_root+'.ci/doxygen.groovy') + doxygen(config) + } + } + }, CodeAnalysis: { + if (!config.nightly_arduino_ide) { + if (config.is_pull_request) { + def analysis = load(config.repository_root+'.ci/static_analysis.groovy') + stage('Cppcheck') { + analysis.cppCheck(config) + } + } + } + }, LinuxBuilds: { + if (!config.nightly_arduino_ide) { + stage('LinuxGwSerial') { + linux.buildSerial(config) + } + stage('LinuxGwEthernet') { + linux.buildEthernet(config) + } + stage('LinuxGwMQTT') { + linux.buildMQTT(config) + } + } + }, ArduinoBuilds: { + lock(quantity: 1, resource: 'arduinoEnv') { + stage('MySensorsMicro (tests)') { + arduino.buildMySensorsMicro(config, config.tests, 'Tests') + } + stage('MySensorsGW (tests)') { + arduino.buildMySensorsGw(config, config.tests, 'Tests') + } + stage('nRF52832 (tests)') { + arduino.buildnRF52832(config, config.tests, 'Tests') + } + stage('nRF51822 (tests)') { + arduino.buildnRF51822(config, config.tests, 'Tests') + } + stage('nRF5 (tests)') { + arduino.buildnRF5(config, config.tests, 'Tests') + } + stage('ESP8266 (tests)') { + arduino.buildEsp8266(config, config.tests, 'Tests') + } + stage('ArduinoNano (tests)') { + arduino.buildArduinoNano(config, config.tests, 'Tests') + } + stage('ArduinoUno (tests)') { + arduino.buildArduinoUno(config, config.tests, 'Tests') + } + stage('ArduinoProMini (tests)') { + arduino.buildArduinoPro(config, config.tests, 'Tests') + } + stage('ArduinoMega (tests)') { + arduino.buildArduinoMega(config, config.tests, 'Tests') + } + stage('MySensorsMicro (examples)') { + arduino.buildMySensorsMicro(config, config.examples, 'Examples') + } + stage('MySensorsGW (examples)') { + arduino.buildMySensorsGw(config, config.examples, 'Examples') + } + // No point in building examples for nRF52832 yet + /* + stage('nRF52832 (examples)') { + arduino.buildnRF52832(config, config.examples, 'Examples') + } + */ + // No point in building examples for nRF51822 yet + /* + stage('nRF51822 (examples)') { + arduino.buildnRF51822(config, config.examples, 'Examples') + } + */ + stage('nRF5 (examples)') { + arduino.buildnRF5(config, config.examples, 'Examples') + } + stage('ESP8266 (examples)') { + arduino.buildEsp8266(config, config.examples, 'Examples') + } + stage('ArduinoNano (examples)') { + arduino.buildArduinoNano(config, config.examples, 'Examples') + } + stage('ArduinoUno (examples)') { + arduino.buildArduinoUno(config, config.examples, 'Examples') + } + stage('ArduinoProMini (examples)') { + arduino.buildArduinoPro(config, config.examples, 'Examples') + } + stage('ArduinoMega (examples)') { + arduino.buildArduinoMega(config, config.examples, 'Examples') + } + } + }, failFast: true + } + } catch(ex) { + currentBuild.result = 'FAILURE' + throw ex + } finally { + if (currentBuild.result != 'SUCCESS') + { + config.pr.setBuildStatus(config, 'ERROR', 'Toll gate', 'Failed', '${BUILD_URL}flowGraphTable/') + if (config.is_pull_request) { + slackSend color: 'danger', + message: "Job '${env.JOB_NAME} <${env.BUILD_URL}|#${env.BUILD_NUMBER}> <${env.CHANGE_URL}|PR#${env.CHANGE_ID} - ${env.CHANGE_TITLE}>' failed with result ${currentBuild.result}." + emailext ( + subject: "Job '${env.JOB_NAME} #${env.BUILD_NUMBER}' failed", + body: """Job '${env.JOB_NAME} #${env.BUILD_NUMBER} (PR#${env.CHANGE_ID} - ${env.CHANGE_TITLE})' ended with result ${currentBuild.result}. +
Check attached console output or here for a hint on what the problem might be.""", + mimeType: 'text/html', to: env.CHANGE_AUTHOR_EMAIL, attachLog: true, compressLog: false + ) + } else { + slackSend color: 'danger', + message: "Job '${env.JOB_NAME} <${env.BUILD_URL}|#${env.BUILD_NUMBER}> ${env.BRANCH_NAME}]' failed with result ${currentBuild.result}." + emailext ( + subject: "Job '${env.JOB_NAME} #${env.BUILD_NUMBER} ${env.BRANCH_NAME}' failed", + body: """Job '${env.JOB_NAME} #${env.BUILD_NUMBER} (${env.BRANCH_NAME})' ended with result ${currentBuild.result}. +
Check attached console output or here for a hint on what the problem might be.""", + mimeType: 'text/html', to: 'builds@mysensors.org', attachLog: true, compressLog: false + ) + } + } + else + { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate', 'Pass', '') + } + } +} +return this \ No newline at end of file diff --git a/.ci/pr-toolbox.groovy b/.ci/pr-toolbox.groovy new file mode 100644 index 00000000..52b2ce05 --- /dev/null +++ b/.ci/pr-toolbox.groovy @@ -0,0 +1,16 @@ +#!groovy +def setBuildStatus(config, String state, String context, String message, String backref) { + if (config.is_pull_request) { + step([$class: 'GitHubCommitStatusSetter', + reposSource: [$class: 'ManuallyEnteredRepositorySource', url: "https://github.com/${config.github_organization}/${config.repository_name}"], + errorHandlers: [[$class: 'ShallowAnyErrorHandler']], + contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context], + commitShaSource: [$class: "ManuallyEnteredShaSource", sha: config.git_sha], + statusBackrefSource: [$class: 'ManuallyEnteredBackrefSource', backref: backref], + statusResultSource: [$class: 'ConditionalStatusResultSource', + results: [[$class: 'AnyBuildResult', message: message, state: state]]]] + ) + } +} + +return this \ No newline at end of file diff --git a/.ci/static_analysis.groovy b/.ci/static_analysis.groovy new file mode 100644 index 00000000..41725cc9 --- /dev/null +++ b/.ci/static_analysis.groovy @@ -0,0 +1,40 @@ +#!groovy +def cppCheck(config) { + config.pr.setBuildStatus(config, 'PENDING', 'Toll gate (Code analysis - Cppcheck)', 'Running...', '${BUILD_URL}flowGraphTable/') + // We could consider running Cppcheck for GNU as well, but to avoid so many duplicates, we stick to AVR + sh """#!/bin/bash +x + cd ${config.repository_root} + echo "Doing cppcheck for AVR..." + git diff --name-only origin/${env.CHANGE_TARGET}..${config.git_sha} | sed '/.ci\\/gitler.sh/d' | sed '/README.md/d' | cppcheck -j 4 --file-list=- --enable=style,information --platform=.mystools/cppcheck/config/avr.xml --suppressions-list=.mystools/cppcheck/config/suppressions.cfg --includes-file=.mystools/cppcheck/config/includes.cfg --language=c++ --xml --xml-version=2 2> cppcheck-avr.xml + cppcheck-htmlreport --file="cppcheck-avr.xml" --title="cppcheck-avr" --report-dir=cppcheck-avr_cppcheck_reports --source-dir=.""" + + publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, + reportDir: config.repository_root+'cppcheck-avr_cppcheck_reports', + reportFiles: 'index.html', reportName: 'CppCheck AVR', reportTitles: '']) + + step([$class: 'ViolationsToGitHubRecorder', + config: [ + repositoryName: config.repository_name, + pullRequestId: env.CHANGE_ID, + createCommentWithAllSingleFileComments: true, + createSingleFileComments: true, + commentOnlyChangedContent: true, + keepOldComments: false, + violationConfigs: [[pattern: '.*/cppcheck-avr\\.xml$', parser: 'CPPCHECK', reporter: 'Cppcheck'],] + ] + ]) + ret = sh(returnStatus: true, + script: "#!/bin/bash +e\n"+ + "cd ${config.repository_root}\n"+ + "grep -q \"0total\" cppcheck-avr_cppcheck_reports/index.html || exit_code=\$?\n"+ + "exit \$((exit_code == 0 ? 0 : 1))") + if (ret == 1) { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Code analysis - Cppcheck)', 'Issues found (but are not considered critical)', '${BUILD_URL}CppCheck_AVR/index.html') + //currentBuild.result = 'UNSTABLE' + //error 'Terminating due to Cppcheck error' + } else { + config.pr.setBuildStatus(config, 'SUCCESS', 'Toll gate (Code analysis - Cppcheck)', 'Pass', '') + } +} + +return this \ No newline at end of file diff --git a/README.md b/README.md index 301da5da..18ea2124 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ Please visit www.mysensors.org for more information Doxygen ------- -[master](https://ci.mysensors.org/job/Verifiers/job/MySensors/branch/master/Doxygen_HTML/index.html) [development](https://ci.mysensors.org/job/Verifiers/job/MySensors/branch/development/Doxygen_HTML/index.html) +[master](https://www.mysensors.org/apidocs/index.html) [development](https://www.mysensors.org/apidocs-beta/index.html) CI statuses ----------- Current build status of master branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/master/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/master/) -Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/development/) +Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/MySensors/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/MySensors/job/MySensors/job/development/) Current build status of master branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/) -Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/) \ No newline at end of file +Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/MySensors-nightly-IDE/job/MySensors/job/development/) \ No newline at end of file diff --git a/hal/architecture/MyHw.h b/hal/architecture/MyHw.h index 2afe9ca0..3fd9c366 100644 --- a/hal/architecture/MyHw.h +++ b/hal/architecture/MyHw.h @@ -26,6 +26,10 @@ #ifndef MyHw_h #define MyHw_h +/** + * @def MY_HWID_PADDING_BYTE + * @brief HwID padding byte + */ #define MY_HWID_PADDING_BYTE (0xAAu) // Implement these as functions or macros