From 8f276368de537a2aba434374aa497f25d24dddd3 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 09:52:56 -0500 Subject: [PATCH 01/11] add OS functions to host --- arduino_ci.gemspec | 2 ++ lib/arduino_ci/host.rb | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/arduino_ci.gemspec b/arduino_ci.gemspec index b25f8ba6..afbe7550 100644 --- a/arduino_ci.gemspec +++ b/arduino_ci.gemspec @@ -23,6 +23,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + spec.add_dependency "os", "~> 1.0" + spec.add_development_dependency "bundler", "~> 1.15" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency 'rubocop', '~>0.49.0' diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 8623bfc1..28e10229 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -1,3 +1,5 @@ +require 'os' + module ArduinoCI # Tools for interacting with the host machine @@ -31,5 +33,11 @@ def self.run(*args, **kwargs) ret end + def self.os + return :osx if OS.osx? + return :linux if OS.linux? + return :windows if OS.windows? + end + end end From 19cd4a806658f4d889b02662f38f2cb48bc59dbe Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:02:48 -0500 Subject: [PATCH 02/11] refactor command structure to allow binary-specific flags --- exe/ci_system_check.rb | 5 +- lib/arduino_ci.rb | 2 +- lib/arduino_ci/arduino_cmd.rb | 133 +++++++++++-------------- lib/arduino_ci/arduino_cmd_linux.rb | 80 +++++++++++++++ lib/arduino_ci/arduino_cmd_osx.rb | 27 +++++ lib/arduino_ci/arduino_installation.rb | 119 +++++++++------------- spec/arduino_cmd_spec.rb | 21 +--- spec/arduino_installation_spec.rb | 6 +- 8 files changed, 220 insertions(+), 173 deletions(-) create mode 100644 lib/arduino_ci/arduino_cmd_linux.rb create mode 100644 lib/arduino_ci/arduino_cmd_osx.rb diff --git a/exe/ci_system_check.rb b/exe/ci_system_check.rb index b738f3c4..c422507c 100755 --- a/exe/ci_system_check.rb +++ b/exe/ci_system_check.rb @@ -1,10 +1,7 @@ require 'arduino_ci' -puts "Enabling display with display manager" -ArduinoCI::DisplayManager::instance.enable - puts "Autlocating Arduino command" -arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! +arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! board_tests = { "arduino:avr:uno" => true, diff --git a/lib/arduino_ci.rb b/lib/arduino_ci.rb index 2f871c67..4a8ef2f1 100644 --- a/lib/arduino_ci.rb +++ b/lib/arduino_ci.rb @@ -1,5 +1,5 @@ require "arduino_ci/version" -require "arduino_ci/arduino_cmd" +require "arduino_ci/arduino_installation" # ArduinoCI contains classes for automated testing of Arduino code on the command line # @author Ian Katz diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index cf88347f..76c1e27a 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -1,41 +1,43 @@ require 'fileutils' require 'arduino_ci/display_manager' -require 'arduino_ci/arduino_installation' module ArduinoCI # Wrap the Arduino executable. This requires, in some cases, a faked display. class ArduinoCmd - class << self - protected :new - - # @return [ArduinoCmd] A command object with a best guess (or nil) for the installation - def autolocate - new(ArduinoInstallation.autolocate) - end - - # @return [ArduinoCmd] A command object, installing Arduino if necessary - def autolocate! - new(ArduinoInstallation.autolocate!) - end - + # Enable a shortcut syntax for command line flags + # @param name [String] What the flag will be called (prefixed with 'flag_') + # @return [void] + # @macro [attach] flag + # @!attribute [r] flag_$1 + # @return String $2 the text of the command line flag + def self.flag(name, text = nil) + text = "(flag #{name} not defined)" if text.nil? + self.class_eval("def flag_#{name};\"#{text}\";end") end attr_accessor :installation - attr_reader :prefs_response_time + attr_accessor :base_cmd + attr_reader :library_is_indexed - # @param installation [ArduinoInstallation] the location of the Arduino program installation - def initialize(installation) - @display_mgr = DisplayManager::instance - @installation = installation - @prefs_response_time = nil + # set the command line flags (undefined for now). + # These vary between gui/cli + flag :get_pref + flag :set_pref + flag :save_prefs + flag :use_board + flag :install_boards + flag :install_library + flag :verify + + def initialize @prefs_cache = nil @library_is_indexed = false end - def _parse_pref_string(arduino_output) + def parse_pref_string(arduino_output) lines = arduino_output.split("\n").select { |l| l.include? "=" } ret = lines.each_with_object({}) do |e, acc| parts = e.split("=", 2) @@ -45,26 +47,21 @@ def _parse_pref_string(arduino_output) ret end - # fetch preferences to a hash - def _prefs - resp = nil - if @installation.requires_x - @display_mgr.with_display do - start = Time.now - resp = run_and_capture("--get-pref") - @prefs_response_time = Time.now - start - end - else - start = Time.now - resp = run_and_capture("--get-pref") - @prefs_response_time = Time.now - start - end + def _lib_dir + "" + end + + # fetch preferences to a string + def _prefs_raw + resp = run_and_capture(flag_get_pref) return nil unless resp[:success] - _parse_pref_string(resp[:out]) + resp[:out] end def prefs - @prefs_cache = _prefs if @prefs_cache.nil? + prefs_raw = _prefs_raw if @prefs_cache.nil? + return nil if prefs_raw.nil? + @prefs_cache = parse_pref_string(prefs_raw) @prefs_cache.clone end @@ -74,42 +71,25 @@ def get_pref(key) data[key] end - # set a preference key/value pair + # underlying preference-setter. + # @return [bool] whether the command succeeded + def _set_pref(key, value) + run_and_capture(flag_set_pref, "#{key}=#{value}", flag_save_prefs)[:success] + end + + # set a preference key/value pair, and update the cache. # @param key [String] the preference key # @param value [String] the preference value # @return [bool] whether the command succeeded def set_pref(key, value) - success = run_with_gui_guess(" about preferences", "--pref", "#{key}=#{value}", "--save-prefs") + success = _set_pref(key, value) @prefs_cache[key] = value if success success end # run the arduino command def run(*args, **kwargs) - full_args = @installation.base_cmd + args - if @installation.requires_x - @display_mgr.run(*full_args, **kwargs) - else - Host.run(*full_args, **kwargs) - end - end - - def run_with_gui_guess(message, *args, **kwargs) - # On Travis CI, we get an error message in the GUI instead of on STDERR - # so, assume that if we don't get a rapid reply that things are not installed - - # if we don't need X, we can skip this whole thing - return run_and_capture(*args, **kwargs)[:success] unless @installation.requires_x - - prefs if @prefs_response_time.nil? - x3 = @prefs_response_time * 3 - Timeout.timeout(x3) do - result = run_and_capture(*args, **kwargs) - result[:success] - end - rescue Timeout::Error - puts "No response in #{x3} seconds. Assuming graphical modal error message#{message}." - false + raise "Ian needs to implement this in a subclass #{args} #{kwargs}" end # run a command and capture its output @@ -140,7 +120,7 @@ def run_wrap(*args, **kwargs) # we do this by just selecting a board. # the arduino binary will error if unrecognized and do a successful no-op if it's installed def board_installed?(boardname) - run_with_gui_guess(" about board not installed", "--board", boardname) + run_and_capture(flag_use_board, boardname)[:success] end # install a board by name @@ -148,22 +128,21 @@ def board_installed?(boardname) # @return [bool] whether the command succeeded def install_board(boardname) # TODO: find out why IO.pipe fails but File::NULL succeeds :( - run_and_capture("--install-boards", boardname, out: File::NULL)[:success] + run_and_capture(flag_install_boards, boardname, out: File::NULL)[:success] end # install a library by name # @param name [String] the library name # @return [bool] whether the command succeeded def install_library(library_name) - result = run_and_capture("--install-library", library_name) + result = run_and_capture(flag_install_library, library_name) @library_is_indexed = true if result[:success] result[:success] end # generate the (very likely) path of a library given its name def library_path(library_name) - sketchbook = get_pref("sketchbook.path") - File.join(sketchbook, library_name) + File.join(_lib_dir, library_name) end # update the library index @@ -175,7 +154,7 @@ def update_library_index # use a particular board for compilation def use_board(boardname) - run_with_gui_guess(" about board not installed", "--board", boardname, "--save-prefs") + run_and_capture(flag_use_board, boardname, flag_save_prefs)[:success] end # use a particular board for compilation, installing it if necessary @@ -197,25 +176,25 @@ def verify_sketch(path) puts "Can't verify nonexistent Sketch at '#{path}'!" return false end - run("--verify", path, err: :out) + run(flag_verify, path, err: :out) end # ensure that the given library is installed, or symlinked as appropriate # return the path of the prepared library, or nil - def install_local_library(library_path) - library_name = File.basename(library_path) - destination_path = File.join(@installation.lib_dir, library_name) + def install_local_library(path) + library_name = File.basename(path) + destination_path = library_path(library_name) # things get weird if the sketchbook contains the library. # check that first if File.exist? destination_path uhoh = "There is already a library '#{library_name}' in the library directory" - return destination_path if destination_path == library_path + return destination_path if destination_path == path # maybe it's a symlink? that would be OK if File.symlink?(destination_path) - return destination_path if File.readlink(destination_path) == library_path - puts "#{uhoh} and it's not symlinked to #{library_path}" + return destination_path if File.readlink(destination_path) == path + puts "#{uhoh} and it's not symlinked to #{path}" return nil end @@ -224,7 +203,7 @@ def install_local_library(library_path) end # install the library - FileUtils.ln_s(library_path, destination_path) + FileUtils.ln_s(path, destination_path) destination_path end diff --git a/lib/arduino_ci/arduino_cmd_linux.rb b/lib/arduino_ci/arduino_cmd_linux.rb new file mode 100644 index 00000000..2fcf26d0 --- /dev/null +++ b/lib/arduino_ci/arduino_cmd_linux.rb @@ -0,0 +1,80 @@ +require 'arduino_ci/arduino_cmd' + +module ArduinoCI + + # Implementation of Arduino linux IDE commands + class ArduinoCmdLinux < ArduinoCmd + + attr_reader :prefs_response_time + + 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" + + def initialize + super + @prefs_response_time = nil + @display_mgr = DisplayManager::instance + end + + # fetch preferences to a hash + def _prefs_raw + start = Time.now + resp = run_and_capture(flag_get_pref) + @prefs_response_time = Time.now - start + return nil unless resp[:success] + resp[:out] + end + + def _lib_dir + File.join(get_pref("sketchbook.path"), "libraries") + end + + # run the arduino command + def run(*args, **kwargs) + full_args = @base_cmd + args + @display_mgr.run(*full_args, **kwargs) + end + + def run_with_gui_guess(message, *args, **kwargs) + # On Travis CI, we get an error message in the GUI instead of on STDERR + # so, assume that if we don't get a rapid reply that things are not installed + + prefs if @prefs_response_time.nil? + x3 = @prefs_response_time * 3 + Timeout.timeout(x3) do + result = run_and_capture(*args, **kwargs) + result[:success] + end + rescue Timeout::Error + puts "No response in #{x3} seconds. Assuming graphical modal error message#{message}." + false + end + + # underlying preference-setter. + # @param key [String] the preference key + # @param value [String] the preference value + # @return [bool] whether the command succeeded + def _set_pref(key, value) + run_with_gui_guess(" about preferences", flag_set_pref, "#{key}=#{value}", flag_save_prefs) + end + + # check whether a board is installed + # we do this by just selecting a board. + # the arduino binary will error if unrecognized and do a successful no-op if it's installed + def board_installed?(boardname) + run_with_gui_guess(" about board not installed", flag_use_board, boardname) + end + + # use a particular board for compilation + def use_board(boardname) + run_with_gui_guess(" about board not installed", flag_use_board, boardname, flag_save_prefs) + end + + end + +end diff --git a/lib/arduino_ci/arduino_cmd_osx.rb b/lib/arduino_ci/arduino_cmd_osx.rb new file mode 100644 index 00000000..d7b9377c --- /dev/null +++ b/lib/arduino_ci/arduino_cmd_osx.rb @@ -0,0 +1,27 @@ +require 'arduino_ci/arduino_cmd' + +module ArduinoCI + + # Implementation of OSX commands + class ArduinoCmdOSX < 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 + def run(*args, **kwargs) + full_args = @base_cmd + args + Host.run(*full_args, **kwargs) + end + + def _lib_dir + File.join(ENV['HOME'], "Documents", "Arduino", "libraries") + end + + end + +end diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index a27b86f0..b49486e0 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -1,4 +1,6 @@ require "arduino_ci/host" +require "arduino_ci/arduino_cmd_osx" +require "arduino_ci/arduino_cmd_linux" DESIRED_ARDUINO_IDE_VERSION = "1.8.5".freeze USE_BUILDER = false @@ -7,104 +9,79 @@ module ArduinoCI # Manage the OS-specific install location of Arduino class ArduinoInstallation - attr_accessor :base_cmd - attr_accessor :lib_dir - attr_accessor :requires_x class << self def force_install_location File.join(ENV['HOME'], 'arduino_ci_ide') end - def from_forced_install - ret = new - builder = File.join(force_install_location, "arduino-builder") - if USE_BUILDER && File.exist?(builder) - ret.base_cmd = [builder] - ret.requires_x = false - else - ret.base_cmd = [File.join(force_install_location, "arduino")] - ret.requires_x = true + # attempt to find a workable Arduino executable across platforms + def autolocate + case Host.os + when :osx then autolocate_osx + when :linux then autolocate_linux end - ret.lib_dir = File.join(force_install_location, "libraries") - # TODO: "libraries" is what's in the adafruit install.sh script - ret end - # attempt to find a workable Arduino executable across platforms - def autolocate + def autolocate_osx osx_root = "/Applications/Arduino.app" old_way = false - if File.exist? osx_root - ret = new - osx_place = "#{osx_root}/Contents/MacOS" + return nil unless File.exist? osx_root - if old_way - ret.base_cmd = [File.join(osx_place, "Arduino")] - else - jvm_runtime = `/usr/libexec/java_home` - ret.base_cmd = [ - "java", - "-cp", "#{osx_root}/Contents/Java/*", - "-DAPP_DIR=#{osx_root}/Contents/Java", - "-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:#{jvm_runtime}/Contents/Home/jre/lib/ext/", - "-Dfile.encoding=UTF-8", - "-Dapple.awt.UIElement=true", - "-Xms128M", - "-Xmx512M", - "processing.app.Base", - ] - end - ret.lib_dir = File.join(osx_place, "Libraries") # TODO: probably wrong - ret.requires_x = false - return ret - end + ret = ArduinoCmdOSX.new + osx_place = "#{osx_root}/Contents/MacOS" - # AAARRRRGGGGHHH - # Even though arduino-builder is an awesome CLI for Arduino, - # ALL THE OPTIONS ARE DIFFERENT (single vs double dash for flags) - # USELESS FOR THE TIME BEING - posix_place = Host.which("arduino-builder") - if USE_BUILDER && !posix_place.nil? - ret = new - ret.base_cmd = [posix_place] - ret.lib_dir = File.join(ENV['HOME'], "Sketchbook") # assume linux - ret.requires_x = false - # https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library - return ret + if old_way + ret.base_cmd = [File.join(osx_place, "Arduino")] + else + jvm_runtime = `/usr/libexec/java_home` + ret.base_cmd = [ + "java", + "-cp", "#{osx_root}/Contents/Java/*", + "-DAPP_DIR=#{osx_root}/Contents/Java", + "-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:#{jvm_runtime}/Contents/Home/jre/lib/ext/", + "-Dfile.encoding=UTF-8", + "-Dapple.awt.UIElement=true", + "-Xms128M", + "-Xmx512M", + "processing.app.Base", + ] end + ret + end - posix_place = Host.which("arduino") - unless posix_place.nil? - ret = new - ret.base_cmd = [posix_place] - ret.lib_dir = File.join(ENV['HOME'], "Sketchbook") # assume linux - ret.requires_x = true - # https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library + def autolocate_linux + gui_name = "arduino" + gui_place = Host.which(gui_name) + unless gui_place.nil? + ret = ArduinoCmdLinux.new + forced_arduino = File.join(force_install_location, gui_name) + forced = File.exist?(forced_arduino) + ret.base_cmd = [(forced ? forced_arduino : gui_place)] return ret end - - return from_forced_install if File.exist? force_install_location - - new + nil end # Attempt to find a workable Arduino executable across platforms, and install it if we don't def autolocate! candidate = autolocate - return candidate unless candidate.base_cmd.nil? + return candidate unless candidate.nil? # force the install - candidate = from_forced_install if force_install - candidate + force_install + autolocate end def force_install - pkgname = "arduino-#{DESIRED_ARDUINO_IDE_VERSION}" - tarfile = "#{pkgname}-linux64.tar.xz" - system("wget", "https://downloads.arduino.cc/#{tarfile}") - system("tar", "xf", tarfile) - system("mv", pkgname, force_install_location) + case Host.os + when :linux + pkgname = "arduino-#{DESIRED_ARDUINO_IDE_VERSION}" + tarfile = "#{pkgname}-linux64.tar.xz" + system("wget", "https://downloads.arduino.cc/#{tarfile}") + system("tar", "xf", tarfile) + system("mv", pkgname, force_install_location) + end end end diff --git a/spec/arduino_cmd_spec.rb b/spec/arduino_cmd_spec.rb index f35e964b..139a43be 100644 --- a/spec/arduino_cmd_spec.rb +++ b/spec/arduino_cmd_spec.rb @@ -6,24 +6,15 @@ def get_sketch(dir, file) RSpec.describe ArduinoCI::ArduinoCmd do - context "autolocate" do - it "Finds the Arduino executable" do - arduino_cmd = ArduinoCI::ArduinoCmd.autolocate - end - end - - context "autolocate!" do - it "Finds the Arduino executable" do - arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! - expect(arduino_cmd.installation.base_cmd).not_to be nil + arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! + context "initialize" do + it "sets base vars" do + expect(arduino_cmd.base_cmd).not_to be nil expect(arduino_cmd.prefs.class).to be Hash - expect(arduino_cmd.prefs_response_time).not_to be nil end end context "board_installed?" do - arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! - ArduinoCI::DisplayManager::instance.enable it "Finds installed boards" do uno_installed = arduino_cmd.board_installed? "arduino:avr:uno" expect(uno_installed).to be true @@ -38,8 +29,6 @@ def get_sketch(dir, file) end context "set_pref" do - arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! - ArduinoCI::DisplayManager::instance.enable it "Sets key to what it was before" do upload_verify = arduino_cmd.get_pref("upload.verify") @@ -49,8 +38,6 @@ def get_sketch(dir, file) end context "verify_sketch" do - arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! - ArduinoCI::DisplayManager::instance.enable sketch_path_ino = get_sketch("FakeSketch", "FakeSketch.ino") sketch_path_pde = get_sketch("FakeSketch", "FakeSketch.pde") diff --git a/spec/arduino_installation_spec.rb b/spec/arduino_installation_spec.rb index 6915a027..a7c71a89 100644 --- a/spec/arduino_installation_spec.rb +++ b/spec/arduino_installation_spec.rb @@ -15,9 +15,9 @@ context "autolocate!" do it "doesn't fail" do - installation = ArduinoCI::ArduinoInstallation.autolocate! - expect(installation.base_cmd).not_to be nil - expect(installation.lib_dir).not_to be nil + arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! + expect(arduino_cmd.base_cmd).not_to be nil + expect(arduino_cmd._lib_dir).not_to be nil end end From 4d80d1e6d6e874dcb9a0121030c2bb0c133ca56e Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:16:34 -0500 Subject: [PATCH 03/11] Handle existing install artifacts gracefully --- lib/arduino_ci/arduino_installation.rb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index b49486e0..1098e68a 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -78,9 +78,25 @@ def force_install when :linux pkgname = "arduino-#{DESIRED_ARDUINO_IDE_VERSION}" tarfile = "#{pkgname}-linux64.tar.xz" - system("wget", "https://downloads.arduino.cc/#{tarfile}") - system("tar", "xf", tarfile) - system("mv", pkgname, force_install_location) + if File.exist? tarfile + puts "Arduino tarfile seems to have been downloaded already" + else + puts "Downloading Arduino binary with wget" + system("wget", "https://downloads.arduino.cc/#{tarfile}") + end + + if File.exist? pkgname + puts "Tarfile seems to have been extracted already" + else + puts "Extracting archive with tar" + system("tar", "xf", tarfile) + end + + if File.exist? force_install_location + puts "Arduino binary seems to have already been force-installed" + else + system("mv", pkgname, force_install_location) + end end end From 9e3edb582fa73f1ffc9def379ad7a2d0d1c847ce Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:17:14 -0500 Subject: [PATCH 04/11] Distinguish between force-installed and existing Arduino installs --- lib/arduino_ci/arduino_installation.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 1098e68a..73e0e61e 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -55,9 +55,14 @@ def autolocate_linux gui_place = Host.which(gui_name) unless gui_place.nil? ret = ArduinoCmdLinux.new - forced_arduino = File.join(force_install_location, gui_name) - forced = File.exist?(forced_arduino) - ret.base_cmd = [(forced ? forced_arduino : gui_place)] + ret.base_cmd = [gui_place] + return ret + end + + forced_arduino = File.join(force_install_location, gui_name) + if File.exist?(forced_arduino) + ret = ArduinoCmdLinux.new + ret.base_cmd = [forced_arduino] return ret end nil From f4802de2d18228c470789d92469de42f198c75ff Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:26:45 -0500 Subject: [PATCH 05/11] shorten run status --- lib/arduino_ci/host.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 28e10229..1eb83948 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -29,7 +29,7 @@ def self.run(*args, **kwargs) puts " $ #{shell_vars} #{actual_args.join(' ')}" ret = system(*full_cmd, **kwargs) status = ret ? "succeeded" : "failed" - puts "#{actual_args[0]} has #{status}" + puts "Command '#{File.basename(actual_args[0])}' has #{status}" ret end From bbae1ec230325e60f2c38ccaf26b1f0c75ab9bb4 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:31:04 -0500 Subject: [PATCH 06/11] add disabled linux builder --- lib/arduino_ci/arduino_cmd_linux_builder.rb | 29 +++++++++++++++++++++ lib/arduino_ci/arduino_installation.rb | 18 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 lib/arduino_ci/arduino_cmd_linux_builder.rb diff --git a/lib/arduino_ci/arduino_cmd_linux_builder.rb b/lib/arduino_ci/arduino_cmd_linux_builder.rb new file mode 100644 index 00000000..20e829c3 --- /dev/null +++ b/lib/arduino_ci/arduino_cmd_linux_builder.rb @@ -0,0 +1,29 @@ +require "arduino_ci/host" +require 'arduino_ci/arduino_cmd' + +module ArduinoCI + + # Implementation of Arduino linux CLI commands + class ArduinoCmdLinuxBuilder < ArduinoCmd + + flag :get_pref, "--get-pref" # apparently doesn't exist + flag :set_pref, "--pref" # apparently doesn't exist + flag :save_prefs, "--save-prefs" # apparently doesn't exist + flag :use_board, "-fqbn" + flag :install_boards, "--install-boards" # apparently doesn't exist + flag :install_library, "--install-library" # apparently doesn't exist + flag :verify, "-compile" + + def _lib_dir + File.join(get_pref("sketchbook.path"), "libraries") + end + + # run the arduino command + def run(*args, **kwargs) + full_args = @base_cmd + args + Host.run(*full_args, **kwargs) + end + + end + +end diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 73e0e61e..64d899a0 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -1,6 +1,7 @@ require "arduino_ci/host" require "arduino_ci/arduino_cmd_osx" require "arduino_ci/arduino_cmd_linux" +require "arduino_ci/arduino_cmd_linux_builder" DESIRED_ARDUINO_IDE_VERSION = "1.8.5".freeze USE_BUILDER = false @@ -51,6 +52,23 @@ def autolocate_osx end def autolocate_linux + if USE_BUILDER + builder_name = "arduino-builder" + cli_place = Host.which(builder_name) + unless cli_place.nil? + ret = ArduinoCmdLinuxBuilder.new + ret.base_cmd = [cli_place] + return ret + end + + forced_builder = File.join(force_install_location, builder_name) + if File.exist?(forced_builder) + ret = ArduinoCmdLinuxBuilder.new + ret.base_cmd = [forced_builder] + return ret + end + end + gui_name = "arduino" gui_place = Host.which(gui_name) unless gui_place.nil? From c605832d21eb177fa1f88f828e125ebbe273c5c7 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 10:33:54 -0500 Subject: [PATCH 07/11] fix requires --- lib/arduino_ci/arduino_cmd.rb | 1 - lib/arduino_ci/arduino_cmd_linux.rb | 1 + lib/arduino_ci/arduino_cmd_osx.rb | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index 76c1e27a..9eeba5d9 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -1,5 +1,4 @@ require 'fileutils' -require 'arduino_ci/display_manager' module ArduinoCI diff --git a/lib/arduino_ci/arduino_cmd_linux.rb b/lib/arduino_ci/arduino_cmd_linux.rb index 2fcf26d0..8b1efcd4 100644 --- a/lib/arduino_ci/arduino_cmd_linux.rb +++ b/lib/arduino_ci/arduino_cmd_linux.rb @@ -1,4 +1,5 @@ require 'arduino_ci/arduino_cmd' +require 'arduino_ci/display_manager' module ArduinoCI diff --git a/lib/arduino_ci/arduino_cmd_osx.rb b/lib/arduino_ci/arduino_cmd_osx.rb index d7b9377c..6fb98ef7 100644 --- a/lib/arduino_ci/arduino_cmd_osx.rb +++ b/lib/arduino_ci/arduino_cmd_osx.rb @@ -1,3 +1,4 @@ +require "arduino_ci/host" require 'arduino_ci/arduino_cmd' module ArduinoCI From 394e9852bfb0eb2932ed926b29d0c39518a95c67 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 11:09:28 -0500 Subject: [PATCH 08/11] properly refactor run method --- lib/arduino_ci/arduino_cmd.rb | 9 ++++++++- lib/arduino_ci/arduino_cmd_linux.rb | 5 ++--- lib/arduino_ci/arduino_cmd_linux_builder.rb | 5 ++--- lib/arduino_ci/arduino_cmd_osx.rb | 5 ++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index 9eeba5d9..e5911e21 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -87,10 +87,17 @@ def set_pref(key, value) end # run the arduino command - def run(*args, **kwargs) + def _run(*args, **kwargs) raise "Ian needs to implement this in a subclass #{args} #{kwargs}" end + # build and run the arduino command + def run(*args, **kwargs) + # TODO: detect env!! + full_args = @base_cmd + args + _run(*full_args, **kwargs) + end + # run a command and capture its output # @return [Hash] {:out => String, :err => String, :success => bool} def run_and_capture(*args, **kwargs) diff --git a/lib/arduino_ci/arduino_cmd_linux.rb b/lib/arduino_ci/arduino_cmd_linux.rb index 8b1efcd4..a57d8dcb 100644 --- a/lib/arduino_ci/arduino_cmd_linux.rb +++ b/lib/arduino_ci/arduino_cmd_linux.rb @@ -36,9 +36,8 @@ def _lib_dir end # run the arduino command - def run(*args, **kwargs) - full_args = @base_cmd + args - @display_mgr.run(*full_args, **kwargs) + def _run(*args, **kwargs) + @display_mgr.run(*args, **kwargs) end def run_with_gui_guess(message, *args, **kwargs) diff --git a/lib/arduino_ci/arduino_cmd_linux_builder.rb b/lib/arduino_ci/arduino_cmd_linux_builder.rb index 20e829c3..c31f63f7 100644 --- a/lib/arduino_ci/arduino_cmd_linux_builder.rb +++ b/lib/arduino_ci/arduino_cmd_linux_builder.rb @@ -19,9 +19,8 @@ def _lib_dir end # run the arduino command - def run(*args, **kwargs) - full_args = @base_cmd + args - Host.run(*full_args, **kwargs) + def _run(*args, **kwargs) + Host.run(*args, **kwargs) end end diff --git a/lib/arduino_ci/arduino_cmd_osx.rb b/lib/arduino_ci/arduino_cmd_osx.rb index 6fb98ef7..68b865bf 100644 --- a/lib/arduino_ci/arduino_cmd_osx.rb +++ b/lib/arduino_ci/arduino_cmd_osx.rb @@ -14,9 +14,8 @@ class ArduinoCmdOSX < ArduinoCmd flag :verify, "--verify" # run the arduino command - def run(*args, **kwargs) - full_args = @base_cmd + args - Host.run(*full_args, **kwargs) + def _run(*args, **kwargs) + Host.run(*args, **kwargs) end def _lib_dir From 800501c819aaa37a8f3b82f91c5f85eb41877568 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 11:27:22 -0500 Subject: [PATCH 09/11] add location of gcc binary to installation --- lib/arduino_ci/arduino_cmd.rb | 7 +++++++ lib/arduino_ci/arduino_installation.rb | 14 ++++++++++---- spec/arduino_installation_spec.rb | 8 +++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index e5911e21..f6fcbd76 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -18,6 +18,7 @@ def self.flag(name, text = nil) attr_accessor :installation attr_accessor :base_cmd + attr_accessor :gcc_cmd attr_reader :library_is_indexed @@ -98,6 +99,12 @@ def run(*args, **kwargs) _run(*full_args, **kwargs) end + def run_gcc(*args, **kwargs) + # TODO: detect env!! + full_args = @gcc_cmd + args + _run(*full_args, **kwargs) + end + # run a command and capture its output # @return [Hash] {:out => String, :err => String, :success => bool} def run_and_capture(*args, **kwargs) diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 64d899a0..67407373 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -25,12 +25,12 @@ def autolocate end def autolocate_osx - osx_root = "/Applications/Arduino.app" + osx_root = "/Applications/Arduino.app/Contents" old_way = false return nil unless File.exist? osx_root ret = ArduinoCmdOSX.new - osx_place = "#{osx_root}/Contents/MacOS" + osx_place = "#{osx_root}/MacOS" if old_way ret.base_cmd = [File.join(osx_place, "Arduino")] @@ -38,8 +38,8 @@ def autolocate_osx jvm_runtime = `/usr/libexec/java_home` ret.base_cmd = [ "java", - "-cp", "#{osx_root}/Contents/Java/*", - "-DAPP_DIR=#{osx_root}/Contents/Java", + "-cp", "#{osx_root}/Java/*", + "-DAPP_DIR=#{osx_root}/Java", "-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:#{jvm_runtime}/Contents/Home/jre/lib/ext/", "-Dfile.encoding=UTF-8", "-Dapple.awt.UIElement=true", @@ -48,6 +48,8 @@ def autolocate_osx "processing.app.Base", ] end + + ret.gcc_cmd = [File.join(osx_root, "Java", "hardware", "tools", "avr", "bin", "avr-gcc")] ret end @@ -58,6 +60,7 @@ def autolocate_linux unless cli_place.nil? ret = ArduinoCmdLinuxBuilder.new ret.base_cmd = [cli_place] + ret.gcc_cmd = Host.which("avr-gcc") return ret end @@ -65,6 +68,7 @@ def autolocate_linux if File.exist?(forced_builder) ret = ArduinoCmdLinuxBuilder.new ret.base_cmd = [forced_builder] + ret.gcc_cmd = [File.join(force_install_location, "hardware", "tools", "avr", "bin", "avr-gcc")] return ret end end @@ -74,6 +78,7 @@ def autolocate_linux unless gui_place.nil? ret = ArduinoCmdLinux.new ret.base_cmd = [gui_place] + ret.gcc_cmd = Host.which("avr-gcc") return ret end @@ -81,6 +86,7 @@ def autolocate_linux if File.exist?(forced_arduino) ret = ArduinoCmdLinux.new ret.base_cmd = [forced_arduino] + ret.gcc_cmd = [File.join(force_install_location, "hardware", "tools", "avr", "bin", "avr-gcc")] return ret end nil diff --git a/spec/arduino_installation_spec.rb b/spec/arduino_installation_spec.rb index a7c71a89..861053fa 100644 --- a/spec/arduino_installation_spec.rb +++ b/spec/arduino_installation_spec.rb @@ -14,11 +14,17 @@ end context "autolocate!" do + arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! it "doesn't fail" do - arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! expect(arduino_cmd.base_cmd).not_to be nil + expect(arduino_cmd.gcc_cmd).not_to be nil expect(arduino_cmd._lib_dir).not_to be nil end + + it "produces a working AVR-GCC" do + expect(arduino_cmd.gcc_cmd).not_to be nil + expect(arduino_cmd.run_gcc("--version")).to be true + end end end From d9dee363c9510c784a5c9809f7d35bc3e25b03dd Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 12:59:27 -0500 Subject: [PATCH 10/11] Add support for extracting code files from C++ libs --- lib/arduino_ci.rb | 1 + lib/arduino_ci/cpp_library.rb | 29 +++++++++++++++++++++++++++++ spec/cpp_library_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 lib/arduino_ci/cpp_library.rb create mode 100644 spec/cpp_library_spec.rb diff --git a/lib/arduino_ci.rb b/lib/arduino_ci.rb index 4a8ef2f1..bfd11647 100644 --- a/lib/arduino_ci.rb +++ b/lib/arduino_ci.rb @@ -1,5 +1,6 @@ require "arduino_ci/version" require "arduino_ci/arduino_installation" +require "arduino_ci/cpp_library" # ArduinoCI contains classes for automated testing of Arduino code on the command line # @author Ian Katz diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb new file mode 100644 index 00000000..6fb30d34 --- /dev/null +++ b/lib/arduino_ci/cpp_library.rb @@ -0,0 +1,29 @@ +require 'find' +require "arduino_ci/host" + +HPP_EXTENSIONS = [".hpp", ".hh", ".h", ".hxx", ".h++"].freeze +CPP_EXTENSIONS = [".cpp", ".cc", ".c", ".cxx", ".c++"].freeze + +module ArduinoCI + + # Information about an Arduino CPP library, specifically for compilation purposes + class CppLibrary + + attr_reader :base_dir + + def initialize(base_dir) + @base_dir = base_dir + end + + def cpp_files + Find.find(@base_dir).select { |path| CPP_EXTENSIONS.include?(File.extname(path)) } + end + + def header_dirs + files = Find.find(@base_dir).select { |path| HPP_EXTENSIONS.include?(File.extname(path)) } + files.map { |path| File.dirname(path) }.uniq + end + + end + +end diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb new file mode 100644 index 00000000..bb98968c --- /dev/null +++ b/spec/cpp_library_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +sampleproj_path = File.join(File.dirname(File.dirname(__FILE__)), "SampleProjects") + +RSpec.describe ArduinoCI::CppLibrary do + cpp_lib_path = File.join(sampleproj_path, "DoSomething") + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path) + context "cpp_files" do + it "finds cpp files in directory" do + dosomething_cpp_files = ["DoSomething/do-something.cpp"] + relative_paths = cpp_library.cpp_files.map { |f| f.split("SampleProjects/", 2)[1] } + expect(relative_paths).to match_array(dosomething_cpp_files) + end + end + + context "header_dirs" do + it "finds directories containing h files" do + dosomething_header_dirs = ["DoSomething"] + relative_paths = cpp_library.header_dirs.map { |f| f.split("SampleProjects/", 2)[1] } + expect(relative_paths).to match_array(dosomething_header_dirs) + end + end + +end From cef20cfacc09c0e35156628c88daed7abc6bbfd4 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 17 Jan 2018 15:36:22 -0500 Subject: [PATCH 11/11] add ability to build a library with mock arduino headers --- .gitignore | 3 ++ SampleProjects/DoSomething/do-something.cpp | 1 + cpp/Arduino.h | 30 +++++++++++++++ cpp/math.h | 41 +++++++++++++++++++++ exe/ci_system_check.rb | 5 +++ lib/arduino_ci/arduino_installation.rb | 12 +++--- lib/arduino_ci/cpp_library.rb | 10 +++++ spec/cpp_library_spec.rb | 7 ++++ 8 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 cpp/Arduino.h create mode 100644 cpp/math.h diff --git a/.gitignore b/.gitignore index 75bf4c75..4f89af25 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ vendor # rspec failure tracking .rspec_status + +# C++ stuff +arduino_ci_built.bin diff --git a/SampleProjects/DoSomething/do-something.cpp b/SampleProjects/DoSomething/do-something.cpp index 60f08106..2ceefa55 100644 --- a/SampleProjects/DoSomething/do-something.cpp +++ b/SampleProjects/DoSomething/do-something.cpp @@ -1,3 +1,4 @@ +#include #include int doSomething(void) { millis(); // this line is only here to test that we're able to refer to the builtins diff --git a/cpp/Arduino.h b/cpp/Arduino.h new file mode 100644 index 00000000..12cdc863 --- /dev/null +++ b/cpp/Arduino.h @@ -0,0 +1,30 @@ +/* +Mock Arduino.h library. + +Where possible, variable names from the Arduino library are used to avoid conflicts + +*/ + + +#ifndef ARDUINO_CI_ARDUINO + +#include "math.h" +#define ARDUINO_CI_ARDUINO + +struct unit_test_state { + unsigned long micros; +}; + +struct unit_test_state godmode { + 0, // micros +}; + +unsigned long millis() { + return godmode.micros / 1000; +} + +unsigned long micros() { + return godmode.micros; +} + +#endif diff --git a/cpp/math.h b/cpp/math.h new file mode 100644 index 00000000..f7f6e372 --- /dev/null +++ b/cpp/math.h @@ -0,0 +1,41 @@ +//abs +long abs(long x) { return x > 0 ? x : -x; } +double fabs(double x) { return x > 0 ? x : -x; } + +//max +long max(long a, long b) { return a > b ? a : b; } +double fmax(double a, double b) { return a > b ? a : b; } + +//min +long min(long a, long b) { return a < b ? a : b; } +double fmin(double a, double b) { return a < b ? a : b; } + +//constrain +long constrain(long x, long a, long b) { return max(a, min(b, x)); } +double constrain(double x, double a, double b) { return max(a, min(b, x)); } + +//map +long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +double map(double x, double in_min, double in_max, double out_min, double out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +//sq +long sq(long x) { return x * x; } +double sq(double x) { return x * x; } + +// ??? too lazy to sort these now +//pow +//sqrt + +// http://www.ganssle.com/approx.htm +// http://www.ganssle.com/approx/sincos.cpp +//cos +//sin +//tan + diff --git a/exe/ci_system_check.rb b/exe/ci_system_check.rb index c422507c..fea71cb5 100755 --- a/exe/ci_system_check.rb +++ b/exe/ci_system_check.rb @@ -47,6 +47,11 @@ got_problem = true unless arduino_cmd.verify_sketch(simple_sketch) library_path = File.join(File.dirname(File.dirname(__FILE__)), "SampleProjects", "DoSomething") + +puts "verify a library with arduino mocks" +cpp_library = ArduinoCI::CppLibrary.new(library_path) +got_problem = true unless cpp_library.build(arduino_cmd) + puts "verify the examples of a library (#{library_path})..." puts " - Install the library" installed_library_path = arduino_cmd.install_local_library(library_path) diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 67407373..c7baee0d 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -49,18 +49,20 @@ def autolocate_osx ] end - ret.gcc_cmd = [File.join(osx_root, "Java", "hardware", "tools", "avr", "bin", "avr-gcc")] + hardware_dir = File.join(osx_root, "Java", "hardware") + ret.gcc_cmd = [File.join(hardware_dir, "tools", "avr", "bin", "avr-gcc")] ret end def autolocate_linux + forced_avr = File.join(force_install_location, "hardware", "tools", "avr") if USE_BUILDER builder_name = "arduino-builder" cli_place = Host.which(builder_name) unless cli_place.nil? ret = ArduinoCmdLinuxBuilder.new ret.base_cmd = [cli_place] - ret.gcc_cmd = Host.which("avr-gcc") + ret.gcc_cmd = [Host.which("avr-gcc")] return ret end @@ -68,7 +70,7 @@ def autolocate_linux if File.exist?(forced_builder) ret = ArduinoCmdLinuxBuilder.new ret.base_cmd = [forced_builder] - ret.gcc_cmd = [File.join(force_install_location, "hardware", "tools", "avr", "bin", "avr-gcc")] + ret.gcc_cmd = [File.join(forced_avr, "bin", "avr-gcc")] return ret end end @@ -78,7 +80,7 @@ def autolocate_linux unless gui_place.nil? ret = ArduinoCmdLinux.new ret.base_cmd = [gui_place] - ret.gcc_cmd = Host.which("avr-gcc") + ret.gcc_cmd = [Host.which("avr-gcc")] return ret end @@ -86,7 +88,7 @@ def autolocate_linux if File.exist?(forced_arduino) ret = ArduinoCmdLinux.new ret.base_cmd = [forced_arduino] - ret.gcc_cmd = [File.join(force_install_location, "hardware", "tools", "avr", "bin", "avr-gcc")] + ret.gcc_cmd = [File.join(forced_avr, "bin", "avr-gcc")] return ret end nil diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 6fb30d34..ec516f78 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -3,6 +3,7 @@ HPP_EXTENSIONS = [".hpp", ".hh", ".h", ".hxx", ".h++"].freeze CPP_EXTENSIONS = [".cpp", ".cc", ".c", ".cxx", ".c++"].freeze +ARDUINO_HEADER_DIR = File.expand_path("../../../cpp", __FILE__) module ArduinoCI @@ -24,6 +25,15 @@ def header_dirs files.map { |path| File.dirname(path) }.uniq end + def build_args + ["-I#{ARDUINO_HEADER_DIR}"] + header_dirs.map { |d| "-I#{d}" } + cpp_files + end + + def build(arduino_cmd) + args = ["-c", "-o", "arduino_ci_built.bin"] + build_args + arduino_cmd.run_gcc(*args) + end + end end diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index bb98968c..238a90fe 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -21,4 +21,11 @@ end end + context "build" do + arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate! + it "builds libraries" do + expect(cpp_library.build(arduino_cmd)).to be true + end + end + end