From 0815bd345cae613f4403b6c347a009197964dab4 Mon Sep 17 00:00:00 2001 From: James Foster Date: Wed, 4 Nov 2020 22:56:45 -0800 Subject: [PATCH 1/7] Prevent name clashes with math macros This takes the edits from [here](https://github.com/matthijskooijman/arduino_ci/commit/e356ad78f36720d66ff46551d444f1a3687a484b) and makes them a separate PR. --- cpp/arduino/AvrMath.h | 122 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 11 deletions(-) diff --git a/cpp/arduino/AvrMath.h b/cpp/arduino/AvrMath.h index 308c4640..e366d68f 100644 --- a/cpp/arduino/AvrMath.h +++ b/cpp/arduino/AvrMath.h @@ -1,26 +1,126 @@ #pragma once +#include "ArduinoDefines.h" #include -#define constrain(x,l,h) ((x)<(l)?(l):((x)>(h)?(h):(x))) -#define map(x,inMin,inMax,outMin,outMax) (((x)-(inMin))*((outMax)-(outMin))/((inMax)-(inMin))+outMin) +#ifdef __cplusplus -#define sq(x) ((x)*(x)) +template +auto constrain(const Amt &amt, const Low &low, const High &high) + -> decltype(amt < low ? low : (amt > high ? high : amt)) { + return (amt < low ? low : (amt > high ? high : amt)); +} -#define radians(deg) ((deg)*DEG_TO_RAD) -#define degrees(rad) ((rad)*RAD_TO_DEG) +template +auto map(const X &x, const InMin &inMin, const InMax &inMax, + const OutMin &outMin, const OutMax &outMax) + -> decltype((x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin) { + return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; +} + +template auto radians(const T °) -> decltype(deg * DEG_TO_RAD) { + return deg * DEG_TO_RAD; +} + +template auto degrees(const T &rad) -> decltype(rad * RAD_TO_DEG) { + return rad * RAD_TO_DEG; +} + +template auto sq(const T &x) -> decltype(x * x) { return x * x; } + +template auto abs(const T &x) -> decltype(x > 0 ? x : -x) { + return x > 0 ? x : -x; +} + +template +auto min(const T &a, const L &b) -> decltype((b < a) ? b : a) { + return (b < a) ? b : a; +} + +template +auto max(const T &a, const L &b) -> decltype((b < a) ? b : a) { + return (a < b) ? b : a; +} + +#else // __cplusplus + +#ifdef constrain +#undef constrain +#endif +#define constrain(amt, low, high) \ + ({ \ + __typeof__(amt) _amt = (amt); \ + __typeof__(low) _low = (low); \ + __typeof__(high) _high = (high); \ + (amt < low ? low : (amt > high ? high : amt)); \ + }) + +#ifdef map +#undef map +#endif +#define map(x, inMin, inMax, outMin, outMax) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(inMin) _inMin = (inMin); \ + __typeof__(inMax) _inMax = (inMax); \ + __typeof__(outMin) _outMin = (outMin); \ + __typeof__(outMax) _outMax = (outMax); \ + (_x - _inMin) * (_outMax - _outMin) / (_inMax - _inMin) + _outMin; \ + }) + +#ifdef radians +#undef radians +#endif +#define radians(deg) \ + ({ \ + __typeof__(deg) _deg = (deg); \ + _deg *DEG_TO_RAD; \ + }) + +#ifdef degrees +#undef degrees +#endif +#define degrees(rad) \ + ({ \ + __typeof__(rad) _rad = (rad); \ + _rad *RAD_TO_DEG; \ + }) + +#ifdef sq +#undef sq +#endif +#define sq(x) \ + ({ \ + __typeof__(x) _x = (x); \ + _x *_x; \ + }) #ifdef abs #undef abs #endif -#define abs(x) ((x)>0?(x):-(x)) +#define abs(x) \ + ({ \ + __typeof__(x) _x = (x); \ + _x > 0 ? _x : -_x; \ + }) + +#ifdef min +#undef min +#endif +#define min(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) #ifdef max #undef max #endif -#define max(a,b) ((a)>(b)?(a):(b)) +#define max(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) -#ifdef min -#undef min #endif -#define min(a,b) ((a)<(b)?(a):(b)) - From 893ba2b7eb5565fc20a2bce5704b5ace3974e463 Mon Sep 17 00:00:00 2001 From: James Foster Date: Wed, 4 Nov 2020 22:59:28 -0800 Subject: [PATCH 2/7] Update CHANGELOG.md to describe math macro changes. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdfd1083..c62a27c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci +- Revise math macros to avoid name clashes ### Deprecated From dc4ccff688816bb2cad0744612fcf38788e7e3b6 Mon Sep 17 00:00:00 2001 From: James Foster Date: Thu, 5 Nov 2020 09:59:39 -0800 Subject: [PATCH 3/7] Indent code inside `#ifdef` block. (Note that this will be undone by `clang-format`.) --- cpp/arduino/AvrMath.h | 230 +++++++++++++++++++++--------------------- 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/cpp/arduino/AvrMath.h b/cpp/arduino/AvrMath.h index e366d68f..8154f8e0 100644 --- a/cpp/arduino/AvrMath.h +++ b/cpp/arduino/AvrMath.h @@ -4,123 +4,123 @@ #ifdef __cplusplus -template -auto constrain(const Amt &amt, const Low &low, const High &high) - -> decltype(amt < low ? low : (amt > high ? high : amt)) { - return (amt < low ? low : (amt > high ? high : amt)); -} - -template -auto map(const X &x, const InMin &inMin, const InMax &inMax, - const OutMin &outMin, const OutMax &outMax) - -> decltype((x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin) { - return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; -} - -template auto radians(const T °) -> decltype(deg * DEG_TO_RAD) { - return deg * DEG_TO_RAD; -} - -template auto degrees(const T &rad) -> decltype(rad * RAD_TO_DEG) { - return rad * RAD_TO_DEG; -} - -template auto sq(const T &x) -> decltype(x * x) { return x * x; } - -template auto abs(const T &x) -> decltype(x > 0 ? x : -x) { - return x > 0 ? x : -x; -} - -template -auto min(const T &a, const L &b) -> decltype((b < a) ? b : a) { - return (b < a) ? b : a; -} - -template -auto max(const T &a, const L &b) -> decltype((b < a) ? b : a) { - return (a < b) ? b : a; -} + template + auto constrain(const Amt &amt, const Low &low, const High &high) + -> decltype(amt < low ? low : (amt > high ? high : amt)) { + return (amt < low ? low : (amt > high ? high : amt)); + } + + template + auto map(const X &x, const InMin &inMin, const InMax &inMax, + const OutMin &outMin, const OutMax &outMax) + -> decltype((x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin) { + return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; + } + + template auto radians(const T °) -> decltype(deg * DEG_TO_RAD) { + return deg * DEG_TO_RAD; + } + + template auto degrees(const T &rad) -> decltype(rad * RAD_TO_DEG) { + return rad * RAD_TO_DEG; + } + + template auto sq(const T &x) -> decltype(x * x) { return x * x; } + + template auto abs(const T &x) -> decltype(x > 0 ? x : -x) { + return x > 0 ? x : -x; + } + + template + auto min(const T &a, const L &b) -> decltype((b < a) ? b : a) { + return (b < a) ? b : a; + } + + template + auto max(const T &a, const L &b) -> decltype((b < a) ? b : a) { + return (a < b) ? b : a; + } #else // __cplusplus -#ifdef constrain -#undef constrain -#endif -#define constrain(amt, low, high) \ - ({ \ - __typeof__(amt) _amt = (amt); \ - __typeof__(low) _low = (low); \ - __typeof__(high) _high = (high); \ - (amt < low ? low : (amt > high ? high : amt)); \ - }) - -#ifdef map -#undef map -#endif -#define map(x, inMin, inMax, outMin, outMax) \ - ({ \ - __typeof__(x) _x = (x); \ - __typeof__(inMin) _inMin = (inMin); \ - __typeof__(inMax) _inMax = (inMax); \ - __typeof__(outMin) _outMin = (outMin); \ - __typeof__(outMax) _outMax = (outMax); \ - (_x - _inMin) * (_outMax - _outMin) / (_inMax - _inMin) + _outMin; \ - }) - -#ifdef radians -#undef radians -#endif -#define radians(deg) \ - ({ \ - __typeof__(deg) _deg = (deg); \ - _deg *DEG_TO_RAD; \ - }) - -#ifdef degrees -#undef degrees -#endif -#define degrees(rad) \ - ({ \ - __typeof__(rad) _rad = (rad); \ - _rad *RAD_TO_DEG; \ - }) - -#ifdef sq -#undef sq -#endif -#define sq(x) \ - ({ \ - __typeof__(x) _x = (x); \ - _x *_x; \ - }) - -#ifdef abs -#undef abs -#endif -#define abs(x) \ - ({ \ - __typeof__(x) _x = (x); \ - _x > 0 ? _x : -_x; \ - }) - -#ifdef min -#undef min -#endif -#define min(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a < _b ? _a : _b; \ - }) - -#ifdef max -#undef max -#endif -#define max(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a > _b ? _a : _b; \ - }) + #ifdef constrain + #undef constrain + #endif + #define constrain(amt, low, high) \ + ({ \ + __typeof__(amt) _amt = (amt); \ + __typeof__(low) _low = (low); \ + __typeof__(high) _high = (high); \ + (amt < low ? low : (amt > high ? high : amt)); \ + }) + + #ifdef map + #undef map + #endif + #define map(x, inMin, inMax, outMin, outMax) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(inMin) _inMin = (inMin); \ + __typeof__(inMax) _inMax = (inMax); \ + __typeof__(outMin) _outMin = (outMin); \ + __typeof__(outMax) _outMax = (outMax); \ + (_x - _inMin) * (_outMax - _outMin) / (_inMax - _inMin) + _outMin; \ + }) + + #ifdef radians + #undef radians + #endif + #define radians(deg) \ + ({ \ + __typeof__(deg) _deg = (deg); \ + _deg *DEG_TO_RAD; \ + }) + + #ifdef degrees + #undef degrees + #endif + #define degrees(rad) \ + ({ \ + __typeof__(rad) _rad = (rad); \ + _rad *RAD_TO_DEG; \ + }) + + #ifdef sq + #undef sq + #endif + #define sq(x) \ + ({ \ + __typeof__(x) _x = (x); \ + _x *_x; \ + }) + + #ifdef abs + #undef abs + #endif + #define abs(x) \ + ({ \ + __typeof__(x) _x = (x); \ + _x > 0 ? _x : -_x; \ + }) + + #ifdef min + #undef min + #endif + #define min(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) + + #ifdef max + #undef max + #endif + #define max(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) #endif From 3f38e9ada635af7b20a3ee1bff0a9a67aa06d601 Mon Sep 17 00:00:00 2001 From: James Foster Date: Thu, 5 Nov 2020 18:58:29 -0800 Subject: [PATCH 4/7] Possible fix for #193 It appears that some boards use memory-mapped I/O to read/write pins. To support that we have an array that can hold pin values. A more elaborate approach would be to connect to the pin logs, but this seems like a decent first step (and allows some files to compile that didn't compile before). --- CHANGELOG.md | 1 + .../TestSomething/test/outputRegister.cpp | 23 +++++++++++++++++++ cpp/arduino/Godmode.h | 20 ++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 SampleProjects/TestSomething/test/outputRegister.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index fdfd1083..1f44cc59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Add `__AVR__` to defines when compiling +- Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/SampleProjects/TestSomething/test/outputRegister.cpp b/SampleProjects/TestSomething/test/outputRegister.cpp new file mode 100644 index 00000000..ea6c9b3a --- /dev/null +++ b/SampleProjects/TestSomething/test/outputRegister.cpp @@ -0,0 +1,23 @@ +#include +#include + +// https://github.com/arduino-libraries/Ethernet/blob/master/src/utility/w5100.h#L337 + +unittest(test) +{ + uint8_t ss_pin = 12; + uint8_t ss_port = digitalPinToPort(ss_pin); + assertEqual(12, ss_port); + uint8_t *ss_pin_reg = portOutputRegister(ss_port); + assertEqual(GODMODE()->pMmapPort(ss_port), ss_pin_reg); + uint8_t ss_pin_mask = digitalPinToBitMask(ss_pin); + assertEqual(1, ss_pin_mask); + + assertEqual((int) 1, (int) *ss_pin_reg); // verify initial value + *(ss_pin_reg) &= ~ss_pin_mask; // set SS + assertEqual((int) 0, (int) *ss_pin_reg); // verify value + *(ss_pin_reg) |= ss_pin_mask; // clear SS + assertEqual((int) 1, (int) *ss_pin_reg); // verify value +} + +unittest_main() diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index 12fa1b51..589e53df 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -30,6 +30,14 @@ unsigned long micros(); #define NUM_SERIAL_PORTS 0 #endif +// These definitions allow the following to compile (see issue #193): +// https://github.com/arduino-libraries/Ethernet/blob/master/src/utility/w5100.h:341 +// add padding because some boards (__MK20DX128__) offset from the given address +#define MMAP_PORTS_SIZE (MOCK_PINS_COUNT + 256) +#define digitalPinToBitMask(pin) (1) +#define digitalPinToPort(pin) (pin) +#define portOutputRegister(port) (GODMODE()->pMmapPort(port)) + class GodmodeState { private: struct PortDef { @@ -43,6 +51,8 @@ class GodmodeState { uint8_t mode; }; + uint8_t mmapPorts[MMAP_PORTS_SIZE]; + static GodmodeState* instance; public: @@ -87,12 +97,19 @@ class GodmodeState { spi.readDelayMicros = 0; } + void resetMmapPorts() { + for (int i = 0; i < MMAP_PORTS_SIZE; ++i) { + mmapPorts[i] = 1; + } + } + void reset() { resetClock(); resetPins(); resetInterrupts(); resetPorts(); resetSPI(); + resetMmapPorts(); seed = 1; } @@ -112,6 +129,9 @@ class GodmodeState { return instance->micros; } + uint8_t* pMmapPort(uint8_t port) { return &mmapPorts[port]; } + uint8_t mmapPortValue(uint8_t port) { return mmapPorts[port]; } + // C++ 11, declare as public for better compiler error messages GodmodeState(GodmodeState const&) = delete; void operator=(GodmodeState const&) = delete; From 3c5b83998f8999f7c0732741468568511c017b4c Mon Sep 17 00:00:00 2001 From: James Foster Date: Sat, 7 Nov 2020 19:20:57 -0800 Subject: [PATCH 5/7] Limit mock to AVR. --- cpp/arduino/Godmode.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index 589e53df..de7a299e 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -30,14 +30,6 @@ unsigned long micros(); #define NUM_SERIAL_PORTS 0 #endif -// These definitions allow the following to compile (see issue #193): -// https://github.com/arduino-libraries/Ethernet/blob/master/src/utility/w5100.h:341 -// add padding because some boards (__MK20DX128__) offset from the given address -#define MMAP_PORTS_SIZE (MOCK_PINS_COUNT + 256) -#define digitalPinToBitMask(pin) (1) -#define digitalPinToPort(pin) (pin) -#define portOutputRegister(port) (GODMODE()->pMmapPort(port)) - class GodmodeState { private: struct PortDef { @@ -51,7 +43,7 @@ class GodmodeState { uint8_t mode; }; - uint8_t mmapPorts[MMAP_PORTS_SIZE]; + uint8_t mmapPorts[MOCK_PINS_COUNT]; static GodmodeState* instance; @@ -98,7 +90,7 @@ class GodmodeState { } void resetMmapPorts() { - for (int i = 0; i < MMAP_PORTS_SIZE; ++i) { + for (int i = 0; i < MOCK_PINS_COUNT; ++i) { mmapPorts[i] = 1; } } @@ -159,5 +151,16 @@ void detachInterrupt(uint8_t interrupt); inline void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0) {} inline void noTone(uint8_t _pin) {} +// These definitions allow the following to compile (see issue #193): +// https://github.com/arduino-libraries/Ethernet/blob/master/src/utility/w5100.h:341 +// we allow one byte per port which "wastes" 224 bytes, but makes the code easier +#if defined(__AVR__) + #define digitalPinToBitMask(pin) (1) + #define digitalPinToPort(pin) (pin) + #define portOutputRegister(port) (GODMODE()->pMmapPort(port)) +#else + // we don't (yet) support other boards +#endif + GodmodeState* GODMODE(); From e9d37db98d44806c4885020cb28fea60245aed9a Mon Sep 17 00:00:00 2001 From: James Foster Date: Sat, 7 Nov 2020 19:22:30 -0800 Subject: [PATCH 6/7] Update tests to only test for AVR. --- SampleProjects/TestSomething/test/outputRegister.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SampleProjects/TestSomething/test/outputRegister.cpp b/SampleProjects/TestSomething/test/outputRegister.cpp index ea6c9b3a..fa12255f 100644 --- a/SampleProjects/TestSomething/test/outputRegister.cpp +++ b/SampleProjects/TestSomething/test/outputRegister.cpp @@ -1,9 +1,11 @@ #include #include +// added to fix https://github.com/Arduino-CI/arduino_ci/issues/193 // https://github.com/arduino-libraries/Ethernet/blob/master/src/utility/w5100.h#L337 -unittest(test) +#if defined(__AVR__) +unittest(portOutputRegister) { uint8_t ss_pin = 12; uint8_t ss_port = digitalPinToPort(ss_pin); @@ -19,5 +21,6 @@ unittest(test) *(ss_pin_reg) |= ss_pin_mask; // clear SS assertEqual((int) 1, (int) *ss_pin_reg); // verify value } +#endif unittest_main() From 972ad2840bc2d56adf719b1a6f36b60fe59797b5 Mon Sep 17 00:00:00 2001 From: James Foster Date: Tue, 10 Nov 2020 06:24:32 -0800 Subject: [PATCH 7/7] Rename `arduino_ci_remote.rb` to `arduino_ci.rb` (#195) Update old script to call new script. --- .travis.yml | 2 +- CHANGELOG.md | 1 + README.md | 12 +- REFERENCE.md | 10 +- SampleProjects/DoSomething/.travis.yml | 2 +- SampleProjects/DoSomething/README.md | 2 +- appveyor.yml | 2 +- exe/arduino_ci.rb | 394 ++++++++++++++++++++++++ exe/arduino_ci_remote.rb | 395 +------------------------ 9 files changed, 412 insertions(+), 408 deletions(-) create mode 100644 exe/arduino_ci.rb diff --git a/.travis.yml b/.travis.yml index bc5c137a..2603ca2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,4 +26,4 @@ script: - bundle exec rspec --backtrace - cd SampleProjects/TestSomething - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c5b5ae0..db353f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Revise math macros to avoid name clashes ### Deprecated +- Deprecated `arduino_ci_remote.rb` in favor of `arduino_ci.rb` ### Removed diff --git a/README.md b/README.md index 27b8ce5e..a5e2d716 100644 --- a/README.md +++ b/README.md @@ -94,15 +94,15 @@ $ bundle install --path vendor/bundle # adds packages to local library With that installed, just the following shell command each time you want the tests to execute: ``` -$ bundle exec arduino_ci_remote.rb +$ bundle exec arduino_ci.rb ``` -`arduino_ci_remote.rb` is the main entry point for this library. This command will iterate over all the library's `examples/` and attempt to compile them. If you set up unit tests, it will run those as well. +`arduino_ci.rb` is the main entry point for this library. This command will iterate over all the library's `examples/` and attempt to compile them. If you set up unit tests, it will run those as well. ### Reference -For more information on the usage of `arduino_ci_remote.rb`, see [REFERENCE.md](REFERENCE.md). It contains information such as: +For more information on the usage of `arduino_ci.rb`, see [REFERENCE.md](REFERENCE.md). It contains information such as: * How to configure build options (platforms to test, Arduino library dependencies to install) with an `.arduino-ci.yml` file * Where to put unit test files @@ -121,7 +121,7 @@ The following prerequisites must be fulfilled: ### Testing with remote CI -> **Note:** `arduino_ci_remote.rb` expects to be run from the root directory of your Arduino project library. +> **Note:** `arduino_ci.rb` expects to be run from the root directory of your Arduino project library. #### Travis CI @@ -135,7 +135,7 @@ sudo: false language: ruby script: - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb ``` @@ -149,7 +149,7 @@ Next, you'll need this in `appveyor.yml` in your repo. build: off test_script: - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb ``` ## Known Problems diff --git a/REFERENCE.md b/REFERENCE.md index da04de88..41925bdc 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,6 +1,6 @@ # Build / Test Behavior of Arduino CI -All tests are run via the same command: `bundle exec arduino_ci_remote.rb`. +All tests are run via the same command: `bundle exec arduino_ci.rb`. This script is responsible for detecting and runing all unit tests, on every combination of Arduino platform and C++ compiler. This is followed by attempting to detect and build every example on every "default" Arduino platform. @@ -11,7 +11,7 @@ These defaults are specified in [misc/default.yml](misc/default.yml). You are f ## Directly Overriding Build Behavior (short term use) -When testing locally, it's often advantageous to limit the number of tests that are performed to only those tests that relate to the work you're doing; you'll get a faster turnaround time in seeing the results. For a full listing, see `bundle exec arduino_ci_remote.rb --help`. +When testing locally, it's often advantageous to limit the number of tests that are performed to only those tests that relate to the work you're doing; you'll get a faster turnaround time in seeing the results. For a full listing, see `bundle exec arduino_ci.rb --help`. ### `--skip-unittests` option @@ -233,14 +233,14 @@ For most build environments, the only script that need be executed by the CI sys ```shell # simplest build script bundle install -bundle exec arduino_ci_remote.rb +bundle exec arduino_ci.rb ``` However, more flexible usage is available: ### Custom Versions of external Arduino Libraries -Sometimes you need a fork of an Arduino library instead of the version that will be installed via their GUI. `arduino_ci_remote.rb` won't overwrite existing downloaded libraries with fresh downloads, but it won't fetch the custom versions for you either. +Sometimes you need a fork of an Arduino library instead of the version that will be installed via their GUI. `arduino_ci.rb` won't overwrite existing downloaded libraries with fresh downloads, but it won't fetch the custom versions for you either. If this is the behavior you need, `ensure_arduino_installation.rb` is for you. It ensures that an Arduino binary is available on the system. @@ -261,7 +261,7 @@ git clone https://repository.com/custom_library_repo.git mv custom_library_repo $(bundle exec arduino_library_location.rb) # now run CI -bundle exec arduino_ci_remote.rb +bundle exec arduino_ci.rb ``` Note the use of subshell to execute `bundle exec arduino_library_location.rb`. This command simply returns the directory in which Arduino Libraries are (or should be) installed. diff --git a/SampleProjects/DoSomething/.travis.yml b/SampleProjects/DoSomething/.travis.yml index 63c08085..40b1fa6d 100644 --- a/SampleProjects/DoSomething/.travis.yml +++ b/SampleProjects/DoSomething/.travis.yml @@ -2,4 +2,4 @@ sudo: false language: ruby script: - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb diff --git a/SampleProjects/DoSomething/README.md b/SampleProjects/DoSomething/README.md index 082beee5..2d0877ae 100644 --- a/SampleProjects/DoSomething/README.md +++ b/SampleProjects/DoSomething/README.md @@ -34,7 +34,7 @@ At a minimum, you will need the following lines in your file: language: ruby script: - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb ``` This will install the necessary ruby gem, and run it. There are no command line arguments as of this writing, because all configuration is provided by... diff --git a/appveyor.yml b/appveyor.yml index 23592ca0..af4bd854 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,4 +24,4 @@ test_script: - bundle exec rspec --backtrace - cd SampleProjects\TestSomething - bundle install - - bundle exec arduino_ci_remote.rb + - bundle exec arduino_ci.rb diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb new file mode 100644 index 00000000..12ab224e --- /dev/null +++ b/exe/arduino_ci.rb @@ -0,0 +1,394 @@ +#!/usr/bin/env ruby +require 'arduino_ci' +require 'set' +require 'pathname' +require 'optparse' + +WIDTH = 80 +FIND_FILES_INDENT = 4 + +@failure_count = 0 +@passfail = proc { |result| result ? "✓" : "✗" } + +# Use some basic parsing to allow command-line overrides of config +class Parser + def self.parse(options) + unit_config = {} + output_options = { + skip_unittests: false, + skip_compilation: false, + ci_config: { + "unittest" => unit_config + }, + } + + opt_parser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options]" + + opts.on("--skip-unittests", "Don't run unit tests") do |p| + output_options[:skip_unittests] = p + end + + opts.on("--skip-compilation", "Don't compile example sketches") do |p| + output_options[:skip_compilation] = p + end + + opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p| + unit_config["testfiles"] ||= {} + unit_config["testfiles"]["select"] ||= [] + unit_config["testfiles"]["select"] << p + end + + opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p| + unit_config["testfiles"] ||= {} + unit_config["testfiles"]["reject"] ||= [] + unit_config["testfiles"]["reject"] << p + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end + end + + opt_parser.parse!(options) + output_options + end +end + +# Read in command line options and make them read-only +@cli_options = (Parser.parse ARGV).freeze + +# terminate after printing any debug info. TODO: capture debug info +def terminate(final = nil) + puts "Failures: #{@failure_count}" + unless @failure_count.zero? || final + puts "Last message: #{@arduino_cmd.last_msg}" + puts "========== Stdout:" + puts @arduino_cmd.last_out + puts "========== Stderr:" + puts @arduino_cmd.last_err + end + retcode = @failure_count.zero? ? 0 : 1 + exit(retcode) +end + +# make a nice status line for an action and react to the action +# TODO / note to self: inform_multline is tougher to write +# without altering the signature because it only leaves space +# for the checkmark _after_ the multiline, it doesn't know how +# to make that conditionally the body +# @param message String the text of the progress indicator +# @param multiline boolean whether multiline output is expected +# @param mark_fn block (string) -> string that says how to describe the result +# @param on_fail_msg String custom message for failure +# @param tally_on_fail boolean whether to increment @failure_count +# @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error) +def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail) + line = "#{message}... " + endline = "...#{message} " + if multiline + puts line + else + print line + end + STDOUT.flush + result = yield + mark = mark_fn.nil? ? "" : mark_fn.call(result) + # if multline, put checkmark at full width + print endline if multiline + puts mark.to_s.rjust(WIDTH - line.length, " ") + unless result + puts on_fail_msg unless on_fail_msg.nil? + @failure_count += 1 if tally_on_fail + # print out error messaging here if we've captured it + terminate if abort_on_fail + end + result +end + +# Make a nice status for something that defers any failure code until script exit +def attempt(message, &block) + perform_action(message, false, @passfail, nil, true, false, &block) +end + +# Make a nice status for something that defers any failure code until script exit +def attempt_multiline(message, &block) + perform_action(message, true, @passfail, nil, true, false, &block) +end + +# Make a nice status for something that kills the script immediately on failure +FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze +def assure(message, &block) + perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block) +end + +def assure_multiline(message, &block) + perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block) +end + +def inform(message, &block) + perform_action(message, false, proc { |x| x }, nil, false, false, &block) +end + +def inform_multiline(message, &block) + perform_action(message, true, nil, nil, false, false, &block) +end + +# Assure that a platform exists and return its definition +def assured_platform(purpose, name, config) + platform_definition = config.platform_definition(name) + assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do + !platform_definition.nil? + end + platform_definition +end + +# Return true if the file (or one of the dirs containing it) is hidden +def file_is_hidden_somewhere?(path) + # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator + path.ascend do |part| + return true if part.basename.to_s.start_with? "." + end + false +end + +# print out some files +def display_files(pathname) + # `find` doesn't follow symlinks, so we should instead + realpath = pathname.symlink? ? pathname.readlink : pathname + + # suppress directories and dotfile-based things + all_files = realpath.find.select(&:file?) + non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) } + + # print files with an indent + margin = " " * FIND_FILES_INDENT + non_hidden.each { |p| puts "#{margin}#{p}" } +end + +def install_arduino_library_dependencies(aux_libraries) + aux_libraries.each do |l| + if @arduino_cmd.library_present?(l) + inform("Using pre-existing library") { l.to_s } + else + assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) } + end + end +end + +def perform_unit_tests(file_config) + if @cli_options[:skip_unittests] + inform("Skipping unit tests") { "as requested via command line" } + return + end + config = file_config.with_override_config(@cli_options[:ci_config]) + cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."), + @arduino_cmd.lib_dir, + config.exclude_dirs.map(&Pathname.method(:new))) + + # check GCC + compilers = config.compilers_to_use + assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? } + compilers.each do |gcc_binary| + attempt_multiline("Checking #{gcc_binary} version") do + version = cpp_library.gcc_version(gcc_binary) + next nil unless version + + puts version.split("\n").map { |l| " #{l}" }.join("\n") + version + end + inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) } + end + + # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name + all_platform_info = {} + config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) } + + # iterate boards / tests + if !cpp_library.tests_dir.exist? + inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do + puts " In case that's an error, this is what was found in the library:" + display_files(cpp_library.tests_dir.parent) + true + end + elsif cpp_library.test_files.empty? + inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do + puts " In case that's an error, this is what was found in the tests directory:" + display_files(cpp_library.tests_dir) + true + end + elsif config.platforms_to_unittest.empty? + inform("Skipping unit tests") { "no platforms were requested" } + else + install_arduino_library_dependencies(config.aux_libraries_for_unittest) + + config.platforms_to_unittest.each do |p| + config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path| + unittest_name = unittest_path.basename.to_s + compilers.each do |gcc_binary| + attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do + exe = cpp_library.build_for_test_with_configuration( + unittest_path, + config.aux_libraries_for_unittest, + gcc_binary, + config.gcc_config(p) + ) + puts + unless exe + puts "Last command: #{cpp_library.last_cmd}" + puts cpp_library.last_out + puts cpp_library.last_err + next false + end + cpp_library.run_test_file(exe) + end + end + end + end + end +end + +def perform_compilation_tests(config) + if @cli_options[:skip_compilation] + inform("Skipping compilation of examples") { "as requested via command line" } + return + end + + # index the existing libraries + attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed + + # initialize library under test + installed_library_path = attempt("Installing library under test") do + @arduino_cmd.install_local_library(Pathname.new(".")) + end + + if !installed_library_path.nil? && installed_library_path.exist? + inform("Library installed at") { installed_library_path.to_s } + else + assure_multiline("Library installed successfully") do + if installed_library_path.nil? + puts @arduino_cmd.last_msg + else + # print out the contents of the deepest directory we actually find + @arduino_cmd.lib_dir.ascend do |path_part| + next unless path_part.exist? + + break display_files(path_part) + end + false + end + end + end + library_examples = @arduino_cmd.library_examples(installed_library_path) + + # gather up all required boards for compilation so we can install them up front. + # start with the "platforms to unittest" and add the examples + # while we're doing that, get the aux libraries as well + example_platform_info = {} + board_package_url = {} + aux_libraries = Set.new(config.aux_libraries_for_build) + # while collecting the platforms, ensure they're defined + + library_examples.each do |path| + ovr_config = config.from_example(path) + ovr_config.platforms_to_build.each do |platform| + # assure the platform if we haven't already + next if example_platform_info.key?(platform) + + platform_info = assured_platform("library example", platform, config) + next if platform_info.nil? + + example_platform_info[platform] = platform_info + package = platform_info[:package] + board_package_url[package] = ovr_config.package_url(package) + end + aux_libraries.merge(ovr_config.aux_libraries_for_build) + end + + # with all platform info, we can extract unique packages and their urls + # do that, set the URLs, and download the packages + all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?) + + # inform about builtin packages + all_packages.select { |p| config.package_builtin?(p) }.each do |p| + inform("Using built-in board package") { p } + end + + # make sure any non-builtin package has a URL defined + all_packages.reject { |p| config.package_builtin?(p) }.each do |p| + assure("Board package #{p} has a defined URL") { board_package_url[p] } + end + + # set up all the board manager URLs. + # we can safely reject nils now, they would be for the builtins + all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?) + + unless all_urls.empty? + assure("Setting board manager URLs") do + @arduino_cmd.board_manager_urls = all_urls + end + end + + all_packages.each do |p| + assure("Installing board package #{p}") do + @arduino_cmd.install_boards(p) + end + end + + install_arduino_library_dependencies(aux_libraries) + + last_board = nil + if config.platforms_to_build.empty? + inform("Skipping builds") { "no platforms were requested" } + return + elsif library_examples.empty? + inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do + display_files(installed_library_path) + end + return + end + + attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") } + + # switching boards takes time, so iterate board first + # _then_ whichever examples match it + examples_by_platform = library_examples.each_with_object({}) do |example_path, acc| + ovr_config = config.from_example(example_path) + ovr_config.platforms_to_build.each do |p| + acc[p] = [] unless acc.key?(p) + acc[p] << example_path + end + end + + examples_by_platform.each do |platform, example_paths| + board = example_platform_info[platform][:board] + assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board + last_board = board + + example_paths.each do |example_path| + example_name = File.basename(example_path) + attempt("Verifying #{example_name}") do + ret = @arduino_cmd.verify_sketch(example_path) + unless ret + puts + puts "Last command: #{@arduino_cmd.last_msg}" + puts @arduino_cmd.last_err + end + ret + end + end + end + +end + +# initialize command and config +config = ArduinoCI::CIConfig.default.from_project_library + +@arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! +inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s } + +perform_unit_tests(config) +perform_compilation_tests(config) + +terminate(true) diff --git a/exe/arduino_ci_remote.rb b/exe/arduino_ci_remote.rb index 12ab224e..e76226b6 100755 --- a/exe/arduino_ci_remote.rb +++ b/exe/arduino_ci_remote.rb @@ -1,394 +1,3 @@ #!/usr/bin/env ruby -require 'arduino_ci' -require 'set' -require 'pathname' -require 'optparse' - -WIDTH = 80 -FIND_FILES_INDENT = 4 - -@failure_count = 0 -@passfail = proc { |result| result ? "✓" : "✗" } - -# Use some basic parsing to allow command-line overrides of config -class Parser - def self.parse(options) - unit_config = {} - output_options = { - skip_unittests: false, - skip_compilation: false, - ci_config: { - "unittest" => unit_config - }, - } - - opt_parser = OptionParser.new do |opts| - opts.banner = "Usage: #{File.basename(__FILE__)} [options]" - - opts.on("--skip-unittests", "Don't run unit tests") do |p| - output_options[:skip_unittests] = p - end - - opts.on("--skip-compilation", "Don't compile example sketches") do |p| - output_options[:skip_compilation] = p - end - - opts.on("--testfile-select=GLOB", "Unit test file (or glob) to select") do |p| - unit_config["testfiles"] ||= {} - unit_config["testfiles"]["select"] ||= [] - unit_config["testfiles"]["select"] << p - end - - opts.on("--testfile-reject=GLOB", "Unit test file (or glob) to reject") do |p| - unit_config["testfiles"] ||= {} - unit_config["testfiles"]["reject"] ||= [] - unit_config["testfiles"]["reject"] << p - end - - opts.on("-h", "--help", "Prints this help") do - puts opts - exit - end - end - - opt_parser.parse!(options) - output_options - end -end - -# Read in command line options and make them read-only -@cli_options = (Parser.parse ARGV).freeze - -# terminate after printing any debug info. TODO: capture debug info -def terminate(final = nil) - puts "Failures: #{@failure_count}" - unless @failure_count.zero? || final - puts "Last message: #{@arduino_cmd.last_msg}" - puts "========== Stdout:" - puts @arduino_cmd.last_out - puts "========== Stderr:" - puts @arduino_cmd.last_err - end - retcode = @failure_count.zero? ? 0 : 1 - exit(retcode) -end - -# make a nice status line for an action and react to the action -# TODO / note to self: inform_multline is tougher to write -# without altering the signature because it only leaves space -# for the checkmark _after_ the multiline, it doesn't know how -# to make that conditionally the body -# @param message String the text of the progress indicator -# @param multiline boolean whether multiline output is expected -# @param mark_fn block (string) -> string that says how to describe the result -# @param on_fail_msg String custom message for failure -# @param tally_on_fail boolean whether to increment @failure_count -# @param abort_on_fail boolean whether to abort immediately on failure (i.e. if this is a fatal error) -def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abort_on_fail) - line = "#{message}... " - endline = "...#{message} " - if multiline - puts line - else - print line - end - STDOUT.flush - result = yield - mark = mark_fn.nil? ? "" : mark_fn.call(result) - # if multline, put checkmark at full width - print endline if multiline - puts mark.to_s.rjust(WIDTH - line.length, " ") - unless result - puts on_fail_msg unless on_fail_msg.nil? - @failure_count += 1 if tally_on_fail - # print out error messaging here if we've captured it - terminate if abort_on_fail - end - result -end - -# Make a nice status for something that defers any failure code until script exit -def attempt(message, &block) - perform_action(message, false, @passfail, nil, true, false, &block) -end - -# Make a nice status for something that defers any failure code until script exit -def attempt_multiline(message, &block) - perform_action(message, true, @passfail, nil, true, false, &block) -end - -# Make a nice status for something that kills the script immediately on failure -FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze -def assure(message, &block) - perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block) -end - -def assure_multiline(message, &block) - perform_action(message, true, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block) -end - -def inform(message, &block) - perform_action(message, false, proc { |x| x }, nil, false, false, &block) -end - -def inform_multiline(message, &block) - perform_action(message, true, nil, nil, false, false, &block) -end - -# Assure that a platform exists and return its definition -def assured_platform(purpose, name, config) - platform_definition = config.platform_definition(name) - assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do - !platform_definition.nil? - end - platform_definition -end - -# Return true if the file (or one of the dirs containing it) is hidden -def file_is_hidden_somewhere?(path) - # this is clunkly but pre-2.2-ish ruby doesn't return ascend as an enumerator - path.ascend do |part| - return true if part.basename.to_s.start_with? "." - end - false -end - -# print out some files -def display_files(pathname) - # `find` doesn't follow symlinks, so we should instead - realpath = pathname.symlink? ? pathname.readlink : pathname - - # suppress directories and dotfile-based things - all_files = realpath.find.select(&:file?) - non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) } - - # print files with an indent - margin = " " * FIND_FILES_INDENT - non_hidden.each { |p| puts "#{margin}#{p}" } -end - -def install_arduino_library_dependencies(aux_libraries) - aux_libraries.each do |l| - if @arduino_cmd.library_present?(l) - inform("Using pre-existing library") { l.to_s } - else - assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) } - end - end -end - -def perform_unit_tests(file_config) - if @cli_options[:skip_unittests] - inform("Skipping unit tests") { "as requested via command line" } - return - end - config = file_config.with_override_config(@cli_options[:ci_config]) - cpp_library = ArduinoCI::CppLibrary.new(Pathname.new("."), - @arduino_cmd.lib_dir, - config.exclude_dirs.map(&Pathname.method(:new))) - - # check GCC - compilers = config.compilers_to_use - assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? } - compilers.each do |gcc_binary| - attempt_multiline("Checking #{gcc_binary} version") do - version = cpp_library.gcc_version(gcc_binary) - next nil unless version - - puts version.split("\n").map { |l| " #{l}" }.join("\n") - version - end - inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) } - end - - # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name - all_platform_info = {} - config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) } - - # iterate boards / tests - if !cpp_library.tests_dir.exist? - inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do - puts " In case that's an error, this is what was found in the library:" - display_files(cpp_library.tests_dir.parent) - true - end - elsif cpp_library.test_files.empty? - inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do - puts " In case that's an error, this is what was found in the tests directory:" - display_files(cpp_library.tests_dir) - true - end - elsif config.platforms_to_unittest.empty? - inform("Skipping unit tests") { "no platforms were requested" } - else - install_arduino_library_dependencies(config.aux_libraries_for_unittest) - - config.platforms_to_unittest.each do |p| - config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path| - unittest_name = unittest_path.basename.to_s - compilers.each do |gcc_binary| - attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do - exe = cpp_library.build_for_test_with_configuration( - unittest_path, - config.aux_libraries_for_unittest, - gcc_binary, - config.gcc_config(p) - ) - puts - unless exe - puts "Last command: #{cpp_library.last_cmd}" - puts cpp_library.last_out - puts cpp_library.last_err - next false - end - cpp_library.run_test_file(exe) - end - end - end - end - end -end - -def perform_compilation_tests(config) - if @cli_options[:skip_compilation] - inform("Skipping compilation of examples") { "as requested via command line" } - return - end - - # index the existing libraries - attempt("Indexing libraries") { @arduino_cmd.index_libraries } unless @arduino_cmd.libraries_indexed - - # initialize library under test - installed_library_path = attempt("Installing library under test") do - @arduino_cmd.install_local_library(Pathname.new(".")) - end - - if !installed_library_path.nil? && installed_library_path.exist? - inform("Library installed at") { installed_library_path.to_s } - else - assure_multiline("Library installed successfully") do - if installed_library_path.nil? - puts @arduino_cmd.last_msg - else - # print out the contents of the deepest directory we actually find - @arduino_cmd.lib_dir.ascend do |path_part| - next unless path_part.exist? - - break display_files(path_part) - end - false - end - end - end - library_examples = @arduino_cmd.library_examples(installed_library_path) - - # gather up all required boards for compilation so we can install them up front. - # start with the "platforms to unittest" and add the examples - # while we're doing that, get the aux libraries as well - example_platform_info = {} - board_package_url = {} - aux_libraries = Set.new(config.aux_libraries_for_build) - # while collecting the platforms, ensure they're defined - - library_examples.each do |path| - ovr_config = config.from_example(path) - ovr_config.platforms_to_build.each do |platform| - # assure the platform if we haven't already - next if example_platform_info.key?(platform) - - platform_info = assured_platform("library example", platform, config) - next if platform_info.nil? - - example_platform_info[platform] = platform_info - package = platform_info[:package] - board_package_url[package] = ovr_config.package_url(package) - end - aux_libraries.merge(ovr_config.aux_libraries_for_build) - end - - # with all platform info, we can extract unique packages and their urls - # do that, set the URLs, and download the packages - all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?) - - # inform about builtin packages - all_packages.select { |p| config.package_builtin?(p) }.each do |p| - inform("Using built-in board package") { p } - end - - # make sure any non-builtin package has a URL defined - all_packages.reject { |p| config.package_builtin?(p) }.each do |p| - assure("Board package #{p} has a defined URL") { board_package_url[p] } - end - - # set up all the board manager URLs. - # we can safely reject nils now, they would be for the builtins - all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?) - - unless all_urls.empty? - assure("Setting board manager URLs") do - @arduino_cmd.board_manager_urls = all_urls - end - end - - all_packages.each do |p| - assure("Installing board package #{p}") do - @arduino_cmd.install_boards(p) - end - end - - install_arduino_library_dependencies(aux_libraries) - - last_board = nil - if config.platforms_to_build.empty? - inform("Skipping builds") { "no platforms were requested" } - return - elsif library_examples.empty? - inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do - display_files(installed_library_path) - end - return - end - - attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") } - - # switching boards takes time, so iterate board first - # _then_ whichever examples match it - examples_by_platform = library_examples.each_with_object({}) do |example_path, acc| - ovr_config = config.from_example(example_path) - ovr_config.platforms_to_build.each do |p| - acc[p] = [] unless acc.key?(p) - acc[p] << example_path - end - end - - examples_by_platform.each do |platform, example_paths| - board = example_platform_info[platform][:board] - assure("Switching to board for #{platform} (#{board})") { @arduino_cmd.use_board(board) } unless last_board == board - last_board = board - - example_paths.each do |example_path| - example_name = File.basename(example_path) - attempt("Verifying #{example_name}") do - ret = @arduino_cmd.verify_sketch(example_path) - unless ret - puts - puts "Last command: #{@arduino_cmd.last_msg}" - puts @arduino_cmd.last_err - end - ret - end - end - end - -end - -# initialize command and config -config = ArduinoCI::CIConfig.default.from_project_library - -@arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! -inform("Located Arduino binary") { @arduino_cmd.binary_path.to_s } - -perform_unit_tests(config) -perform_compilation_tests(config) - -terminate(true) +puts "arduino_ci_remote.rb is deprecated in favor of arduino_ci.rb." +require_relative "arduino_ci.rb"