diff --git a/.gitignore b/.gitignore index 6e35644c..2b5aa6a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /.bundle/ /.yardoc -/Gemfile.lock +Gemfile.lock /_yardoc/ /coverage/ /doc/ diff --git a/.rubocop.yml b/.rubocop.yml index 04aadfbc..069524cb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,9 @@ Layout/EmptyLinesAroundModuleBody: Layout/ExtraSpacing: Enabled: false +Layout/EndOfLine: + EnforcedStyle: lf + Metrics/LineLength: Description: Limit lines to 80 characters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits diff --git a/CHANGELOG.md b/CHANGELOG.md index 16895a8c..bf305622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,17 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Arduino `force_install` on Linux now attempts downloading 3 times and provides more information on failure - Explicit check for `wget` +- Windows / Appveyor support, enabled largely by contributions from @tomduff +- `long long` support in `String` +- Representative `.gitignore` files in sample projects +- Cross-platform symlinking ### Changed - Author - Splash-screen-skip hack on OSX now falls back on "official" launch method if the hack doesn't work - Refactored download/install code in prepration for windows CI +- Explicitly use 32-bit math for mocked Random() ### Deprecated ### Removed ### Fixed +- `Gemfile.lock` files are properly ignored +- Windows hosts won't try to open a display manager ### Security diff --git a/README.md b/README.md index df7b609d..6b01acef 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci) -[![Build Status](https://travis-ci.org/ianfixes/arduino_ci.svg)](https://travis-ci.org/ianfixes/arduino_ci) +[![Linux Build Status](https://travis-ci.org/ianfixes/arduino_ci.svg)](https://travis-ci.org/ianfixes/arduino_ci) +[![Windows Build status](https://ci.appveyor.com/api/projects/status/8f6e39dea319m83q?svg=true)](https://ci.appveyor.com/project/ianfixes/arduino-ci) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/0.1.9) # ArduinoCI Ruby gem (`arduino_ci`) diff --git a/SampleProjects/DoSomething/.arduino-ci.yaml b/SampleProjects/DoSomething/.arduino-ci.yaml index f75da570..59c23ed6 100644 --- a/SampleProjects/DoSomething/.arduino-ci.yaml +++ b/SampleProjects/DoSomething/.arduino-ci.yaml @@ -11,3 +11,5 @@ unittest: - uno - due - leonardo + compilers: + - g++ diff --git a/SampleProjects/DoSomething/.gitignore b/SampleProjects/DoSomething/.gitignore new file mode 100644 index 00000000..2b5aa6a7 --- /dev/null +++ b/SampleProjects/DoSomething/.gitignore @@ -0,0 +1,17 @@ +/.bundle/ +/.yardoc +Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +vendor +*.gem + +# rspec failure tracking +.rspec_status + +# C++ stuff +*.bin +*.bin.dSYM diff --git a/SampleProjects/DoSomething/Gemfile.lock b/SampleProjects/DoSomething/Gemfile.lock deleted file mode 100644 index 01228d8d..00000000 --- a/SampleProjects/DoSomething/Gemfile.lock +++ /dev/null @@ -1,19 +0,0 @@ -PATH - remote: /Users/ikatz/Development/non-wayfair/arduino_ci - specs: - arduino_ci (0.1.7) - os (~> 1.0) - -GEM - remote: https://rubygems.org/ - specs: - os (1.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - arduino_ci! - -BUNDLED WITH - 1.16.0 diff --git a/SampleProjects/TestSomething/.gitignore b/SampleProjects/TestSomething/.gitignore new file mode 100644 index 00000000..2b5aa6a7 --- /dev/null +++ b/SampleProjects/TestSomething/.gitignore @@ -0,0 +1,17 @@ +/.bundle/ +/.yardoc +Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +vendor +*.gem + +# rspec failure tracking +.rspec_status + +# C++ stuff +*.bin +*.bin.dSYM diff --git a/SampleProjects/TestSomething/Gemfile.lock b/SampleProjects/TestSomething/Gemfile.lock deleted file mode 100644 index e1c89622..00000000 --- a/SampleProjects/TestSomething/Gemfile.lock +++ /dev/null @@ -1,19 +0,0 @@ -PATH - remote: /Users/ikatz/Development/non-wayfair/arduino_ci - specs: - arduino_ci (0.1.8) - os (~> 1.0) - -GEM - remote: https://rubygems.org/ - specs: - os (1.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - arduino_ci! - -BUNDLED WITH - 1.16.0 diff --git a/SampleProjects/TestSomething/test/godmode.cpp b/SampleProjects/TestSomething/test/godmode.cpp index 991b829d..9364a07f 100644 --- a/SampleProjects/TestSomething/test/godmode.cpp +++ b/SampleProjects/TestSomething/test/godmode.cpp @@ -17,14 +17,21 @@ unittest(millis_micros_and_delay) unittest(random) { + GodmodeState* state = GODMODE(); + state->reset(); randomSeed(1); + assertEqual(state->seed, 1); + unsigned long x; x = random(4294967293); assertEqual(4294967292, x); + assertEqual(state->seed, 4294967292); x = random(50, 100); - assertEqual(83, x); + assertEqual(87, x); + assertEqual(state->seed, 4294967287); x = random(100); - assertEqual(74, x); + assertEqual(82, x); + assertEqual(state->seed, 4294967282); } void myInterruptHandler() { diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..b810ba1e --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,27 @@ +install: + - set PATH=C:\Ruby22\bin;C:\cygwin\bin;C:\cygwin64\bin;%PATH% + - bundle install + - '%CYG_ROOT%\setup-%CYG_ARCH%.exe -qnNdO -R %CYG_ROOT% -s http://cygwin.mirror.constant.com -l %CYG_ROOT%/var/cache/setup -P autoconf -P automake -P bison -P libgmp-devel -P gcc-core -P gcc-g++ -P mingw-runtime -P mingw-binutils -P mingw-gcc-core -P mingw-gcc-g++ -P mingw-pthreads -P mingw-w32api -P libtool -P make -P gettext-devel -P gettext -P intltool -P libiconv -P pkg-config -P git -P wget -P curl' + +environment: + matrix: + - CYG_ARCH: x86_64 + CYG_ROOT: C:/cygwin64 + +build: off + +before_test: + - ruby -v + - gem -v + - bundle -v + - g++ -v + +test_script: + # https://help.appveyor.com/discussions/problems/5170-progresspreference-not-works-always-shown-preparing-modules-for-first-use-in-stderr + - ps: $ProgressPreference = "SilentlyContinue" + - bundle exec rubocop --version + - bundle exec rubocop -D . + - bundle exec rspec + - cd SampleProjects\TestSomething + - bundle install + - bundle exec arduino_ci_remote.rb diff --git a/arduino_ci.gemspec b/arduino_ci.gemspec index 3ba43660..f4a77ace 100644 --- a/arduino_ci.gemspec +++ b/arduino_ci.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "os", "~> 1.0" + spec.add_dependency "rubyzip", "~> 1.2.1" spec.add_development_dependency "bundler", "~> 1.15" spec.add_development_dependency "rspec", "~> 3.0" diff --git a/cpp/arduino/Godmode.cpp b/cpp/arduino/Godmode.cpp index 2c0c6697..d9e46b52 100644 --- a/cpp/arduino/Godmode.cpp +++ b/cpp/arduino/Godmode.cpp @@ -38,6 +38,7 @@ long random(long vmax) { GodmodeState* godmode = GODMODE(); godmode->seed += 4294967291; // it's a prime that fits in 32 bits + godmode->seed = godmode->seed % 4294967296; // explicitly wrap in case we're on a 64-bit impl return godmode->seed % vmax; } diff --git a/cpp/arduino/WString.h b/cpp/arduino/WString.h index 39474564..498de157 100644 --- a/cpp/arduino/WString.h +++ b/cpp/arduino/WString.h @@ -73,6 +73,8 @@ class String: public string explicit String(unsigned int val , unsigned char base=10): string(mytoa(val, base)) {} explicit String(long val, unsigned char base=10): string(mytoas(val, base)) {} explicit String(unsigned long val, unsigned char base=10): string(mytoa(val, base)) {} + explicit String(long long val, unsigned char base=10): string(mytoas(val, base)) {} + explicit String(unsigned long long val, unsigned char base=10): string(mytoa(val, base)) {} explicit String(float val, unsigned char decimalPlaces=2): string(dtoas(val, decimalPlaces)) {} explicit String(double val, unsigned char decimalPlaces=2): string(dtoas(val, decimalPlaces)) {} @@ -95,6 +97,8 @@ class String: public string unsigned char concat(unsigned int num) { append(String(num)); return 1; } unsigned char concat(long num) { append(String(num)); return 1; } unsigned char concat(unsigned long num) { append(String(num)); return 1; } + unsigned char concat(long long num) { append(String(num)); return 1; } + unsigned char concat(unsigned long long num) { append(String(num)); return 1; } unsigned char concat(float num) { append(String(num)); return 1; } unsigned char concat(double num) { append(String(num)); return 1; } @@ -107,6 +111,8 @@ class String: public string String & operator += (unsigned int num) { concat(num); return *this; } String & operator += (long num) { concat(num); return *this; } String & operator += (unsigned long num) { concat(num); return *this; } + String & operator += (long long num) { concat(num); return *this; } + String & operator += (unsigned long long num) { concat(num); return *this; } String & operator += (float num) { concat(num); return *this; } String & operator += (double num) { concat(num); return *this; } @@ -169,9 +175,15 @@ class String: public string assign(substr(b, e - b + 1)); } - long toInt(void) const { return std::stol(*this); } float toFloat(void) const { return std::stof(*this); } double toDouble(void) const { return std::stod(*this); } + long toInt(void) const { + try { + return std::stol(*this); + } catch (std::out_of_range) { + return std::stoll(*this); + } + } }; diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index d1e16a3a..b3b671df 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -274,7 +274,7 @@ def install_local_library(path) end # install the library - FileUtils.ln_s(realpath, destination_path) + Host.symlink(realpath, destination_path) destination_path end diff --git a/lib/arduino_ci/arduino_cmd_windows.rb b/lib/arduino_ci/arduino_cmd_windows.rb new file mode 100644 index 00000000..52be6f12 --- /dev/null +++ b/lib/arduino_ci/arduino_cmd_windows.rb @@ -0,0 +1,34 @@ +require "arduino_ci/host" +require 'arduino_ci/arduino_cmd' + +module ArduinoCI + + # Implementation of OSX commands + class ArduinoCmdWindows < ArduinoCmd + flag :get_pref, "--get-pref" + flag :set_pref, "--pref" + flag :save_prefs, "--save-prefs" + flag :use_board, "--board" + flag :install_boards, "--install-boards" + flag :install_library, "--install-library" + flag :verify, "--verify" + + # run the arduino command + # @return [bool] whether the command succeeded + def _run_and_output(*args, **kwargs) + Host.run_and_output(*args, **kwargs) + end + + # run the arduino command + # @return [Hash] keys for :success, :out, and :err + def _run_and_capture(*args, **kwargs) + Host.run_and_capture(*args, **kwargs) + end + + def _lib_dir + File.join(ENV['userprofile'], "Documents", "Arduino", "libraries") + end + + end + +end diff --git a/lib/arduino_ci/arduino_downloader_windows.rb b/lib/arduino_ci/arduino_downloader_windows.rb new file mode 100644 index 00000000..417ff49f --- /dev/null +++ b/lib/arduino_ci/arduino_downloader_windows.rb @@ -0,0 +1,107 @@ +require 'base64' +require 'shellwords' # fingers crossed this works on win32 +require 'win32/registry' +require "arduino_ci/arduino_downloader" +require 'open-uri' +require 'zip' +require "fileutils" + +module ArduinoCI + + # Manage the POSIX download & install of Arduino + class ArduinoDownloaderWindows < ArduinoDownloader + + # Make any preparations or run any checks prior to making changes + # @return [string] Error message, or nil if success + def prepare + nil + end + + # The technology that will be used to complete the download + # (for logging purposes) + # @return [string] + def downloader + "open-uri" + end + + # Download the package_url to package_file + # @return [bool] whether successful + def download + # Turned off ssl verification + # This should be acceptable because it won't happen on a user's machine, just CI + open(URI.parse(package_url), ssl_verify_mode: 0) do |url| + File.open(package_file, 'wb') { |file| file.write(url.read) } + end + end + + # Move the extracted package file from extracted_file to the force_install_location + # @return [bool] whether successful + def install + # Move only the content of the directory + FileUtils.mv extracted_file, self.class.force_install_location + # clean up the no longer required root extracted folder + FileUtils.rm_rf extracted_file + end + + # The local filename of the desired IDE package (zip/tar/etc) + # @return [string] + def package_file + "#{extracted_file}-windows.zip" + end + + # The technology that will be used to extract the download + # (for logging purposes) + # @return [string] + def extracter + "Expand-Archive" + end + + # Extract the package_file to extracted_file + # @return [bool] whether successful + def extract + Zip::File.open(package_file) do |zip| + zip.each do |file| + file.extract(file.name) + end + end + # clean up the no longer required zip + FileUtils.rm_rf package_file + end + + # The local file (dir) name of the extracted IDE package (zip/tar/etc) + # @return [string] + def extracted_file + "arduino-#{@desired_ide_version}" + end + + # The path to the directory of an existing installation, or nil + # @return [string] + def self.existing_installation + exe = self.existing_executable + return nil if exe.nil? + File.dirname(exe) + end + + # The executable Arduino file in an existing installation, or nil + # @return [string] + def self.existing_executable + arduino_reg = 'SOFTWARE\WOW6432Node\Arduino' + Win32::Registry::HKEY_LOCAL_MACHINE.open(arduino_reg) do |reg| + path = reg.read_s('Install_Dir') + exe = File.join(path, "arduino_debug.exe") + return exe if File.exist? exe + end + rescue + nil + end + + # The executable Arduino file in a forced installation, or nil + # @return [string] + def self.force_installed_executable + exe = File.join(self.force_install_location, "arduino_debug.exe") + return nil if exe.nil? + exe + end + + end +end diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index ad1cef47..20229467 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -1,9 +1,12 @@ require "arduino_ci/host" require "arduino_ci/arduino_cmd_osx" require "arduino_ci/arduino_cmd_linux" +require "arduino_ci/arduino_cmd_windows" require "arduino_ci/arduino_cmd_linux_builder" -require "arduino_ci/arduino_downloader_linux" require "arduino_ci/arduino_downloader_osx" +require "arduino_ci/arduino_downloader_linux" + +require "arduino_ci/arduino_downloader_windows" if ArduinoCI::Host.os == :windows DESIRED_ARDUINO_IDE_VERSION = "1.8.5".freeze @@ -28,11 +31,11 @@ def autolocate return nil if loc.nil? ret = ArduinoCmdLinux.new ret.base_cmd = [loc] - # when :windows then - # ArduinoDownloaderWindows.autolocation - # return nil if loc.nil? - # ret = ArduinoCmdWindows.new - # ret.base_cmd = [loc] + when :windows then + loc = ArduinoDownloaderWindows.autolocated_executable + return nil if loc.nil? + ret = ArduinoCmdWindows.new + ret.base_cmd = [loc] end ret end @@ -93,7 +96,7 @@ def autolocate! def force_install worker_class = case Host.os when :osx then ArduinoDownloaderOSX - # when :windows then force_install_windows + when :windows then ArduinoDownloaderWindows when :linux then ArduinoDownloaderLinux end worker = worker_class.new(DESIRED_ARDUINO_IDE_VERSION) diff --git a/lib/arduino_ci/display_manager.rb b/lib/arduino_ci/display_manager.rb index 05f8189b..e215a54d 100644 --- a/lib/arduino_ci/display_manager.rb +++ b/lib/arduino_ci/display_manager.rb @@ -34,6 +34,7 @@ def initialize # @return [bool] whether there is already a GUI that can accept windows def existing_display? return true if RUBY_PLATFORM.include? "darwin" + return true if Host.os == :windows return false if ENV["DISPLAY"].nil? return true if ENV["DISPLAY"].include? ":" false diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 7a2983e6..ad7f1293 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -37,5 +37,16 @@ def self.os return :windows if OS.windows? end + # if on windows, call mklink, else self.symlink + # https://stackoverflow.com/a/22716582/2063546 + def self.symlink(old_path, new_path) + return FileUtils.ln_s(old_path, new_path) unless RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/ + + # windows mklink syntax is reverse of unix ln -s + # windows mklink is built into cmd.exe + # vulnerable to command injection, but okay because this is a hack to make a cli tool work. + _stdin, _stdout, _stderr, wait_thr = Open3.popen3('cmd.exe', "/c mklink #{new_path} #{old_path}") + wait_thr.value.exitstatus + end end end