Installing savon gem fails on x86 SU2014/2015

Hello. I’m trying to install savon to connect with a SOAP webservice. Savon requires nokogiri to be installed. In my external ruby 2.0 installation the correct platform is chosen for nokogiri (x86-mingw32) which has precompiled libraries. But Sketchup insists on getting the ‘ruby’ platform version which results in errors of not having the required build tools, but even if it did it wouldnt successfully compile (doesn’t compile in normal ruby200 installation). How can I make sketchup install the correct mingw32 version of nokogiri? The 64-bit 2014 SU installs the correct nokogiri-1.6.6.2-x64-mingw32… Really weird.

I was also told that bundler could help me fix this but I don’t understand how this can be accomplished inside sketchup as there’s no command line.

Thank you

I managed to get the nokogiri-1.6.6.2-x86-mingw32 version to install by modifying rubygems.rb and platform.rb in the following places

# Array of platforms this RubyGems supports.
def self.platforms
        @platforms ||= []
        if @platforms.empty?
          @platforms = ["x86-mingw32", Gem::Platform.local]
        end
        @platforms
      end
-----------------------------------------------------------
  def self.match(platform)
    Gem.platforms.any? do |local_platform|
      platform.nil? or
        local_platform == platform or
        (local_platform != "x86-mingw32" and local_platform =~ platform)
    end
  end

  def self.new(arch) # :nodoc:
    case arch
    when Gem::Platform::CURRENT then
      Gem::Platform.local
    when "x86-mingw32", nil, '' then
      "x86-mingw32"
    else
      super
    end
  end

Then the rest of the gems installed with an unknown prefix but I can require nokogiri and savon with no exceptions thrown. I would like to find a better solution as this is hacking with the SU files which is not that good to do.

When I thought I had at least solved my problem I tried to run a simple savon client that works perfectly in my ruby installation and I get many errors on Sketchup::Console, logger.rb and savon.rb

client = Savon.client(wsdl: "http://www.w3schools.com/webservices/tempconvert.asmx?wsdl")
response = client.call(:celsius_to_fahrenheit, message: { "Celsius" => "25" })
puts response.body

And this is the error log :

Error: #<TypeError: no implicit conversion of Sketchup::Console into String>
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/logger.rb:595:in `exist?'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/logger.rb:595:in `open_logfile'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/logger.rb:553:in `initialize'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/logger.rb:319:in `new'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/logger.rb:319:in `initialize'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon/options.rb:79:in `new'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon/options.rb:79:in `initialize'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon/client.rb:51:in `new'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon/client.rb:51:in `set_globals'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon/client.rb:15:in `initialize'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon.rb:10:in `new'
C:/Users/Popi/AppData/Roaming/SketchUp/SketchUp 2014/SketchUp/Gems/gems/savon-2.11.1-unknown/lib/savon.rb:10:in `client' 

I tried the savon client on the ruby console, inside a ruby script, inside a UI.messagebox() with the same results in all. Please help me, I really need to fix it soon.

The Ruby Console is not a totally equivalent substitute for the standard output IO you get when running Ruby outside SketchUp. Not all of the things that a script might do to its standard output will work. This can interfere with Gems that try to manipulate the output IO. That appears to be what is happening in your case.

I commented out a line in options.rb of savon that had to with Logger and it doesnt throw an error now. Is it possible to install nokogiri and savon without hacking ruby files? Why does the 64bit version install the x64-mingw32 version of nokogiri but the 32bit ver of SU fails to do so for its x86 counterpart? Why can’t I force it to install a specific revision thru the Gem::install method? I see you have removed the third parameter *options which could be used to do something like --platform=x86-mingw32 maybe?

You meant 2015 SketchUp as 2014 did not have a 64-bit edition.

I said (over at SketchUcation,) that this might be a job for bundler.

I’ve never used it inside SketchUp, but have with my 4 system Ruby installs to install all the dependencies for YARD development.

From the “rubygems.rb” file comments:

#
# == RubyGems Defaults, Packaging
#
# RubyGems defaults are stored in rubygems/defaults.rb.  If you're packaging
# RubyGems or implementing Ruby you can change RubyGems' defaults.
#
# For RubyGems packagers, provide lib/rubygems/operating_system.rb and
# override any defaults from lib/rubygems/defaults.rb.
#
# For Ruby implementers, provide lib/rubygems/defaults/#{RUBY_ENGINE}.rb and
# override any defaults from lib/rubygems/defaults.rb.
#
# If you need RubyGems to perform extra work on install or uninstall, your
# defaults override file can set pre and post install and uninstall hooks.
# See Gem::pre_install, Gem::pre_uninstall, Gem::post_install,
# Gem::post_uninstall.
#

None of the above has yet been done for SketchUp. Using gems with SketchUp is a new thing, less than 2 years old really.

NOWHERE does Trimble guarantee that gems that need DevKit can be compiled under SketchUp.
What some of us do is re-package gems after compiling, into SketchUp extension packages.

yes I meant x64 2015, sorry about that. Can you give me any hints about how to access bundler thru the ruby console or how to go about with that?

As for your second post, nokogiri doesnt even compile in vanilla ruby2.0 with devkit, they also say not to try to compile it in windows. What I’m saying is that sketchup should choose the correct x86-mingw32 or x64-mingw32 precompiled version depending on what version of sketchup is installed. As I have demonstrated above, it installs fine.

I’m certain the problem lies in sketchup not being able to put the correct variables @cpu and @os in Gem::Platform when you use 32-bit sketchup on 64-bit windows. When both are x64 it gives @cpu=x64 and @os=mingw32 so the correct nokogiri-x64-mingw32 gem gets installed. But on x86 SU on x64 w7 it gives

Gem::Platform.local
#<Gem::Platform:0x5233d64 @cpu=nil, @os="unknown", @version=nil>

I agree, as I said (above) Trimble Team did not provide a special “RubyStdLib/rubygems/operating_system.rb” file that had the proper overrides.

I did inform Thomas (@tt_su) about this. Maybe it will get done for the next release or the one after ?

The problem for us is that much of the config of Ruby is done when it’s compiled. And getting the Ruby interpreter to function embedded inside a desktop application like SketchUp is very challenging. There is no clear API or documentation for Ruby to do so.

Dan, I struggle to find my notes right now, but did you know of a way to generate a full operating_system.rb to match a Ruby build?

Did anyone try with a custom operating_system.rb to see if that fixed the gem installation issues described in this topic?

@tt_su modified operating_system.rb like this

# :DK-BEG: missing DevKit/build tool convenience notice
Gem.pre_install do |gem_installer|
  if RUBY_PLATFORM == "i386-mingw32"
    Gem::Platform.local.cpu = "x86"
    Gem::Platform.local.os = "mingw32"
  end
  unless gem_installer.spec.extensions.empty?
    begin
      load 'devkit'
...

Then I tried

Gem::install 'mysql'
Error: #<Gem::InstallError: The 'mysql' native gem requires installed build tools.

Please update your PATH to include build tools or download the DevKit
from 'http://rubyinstaller.org/downloads' and follow the instructions
at 'http://github.com/oneclick/rubyinstaller/wiki/Development-Kit'
>
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/defaults/operating_system.rb:16:in `rescue in block in <top (required)>'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/defaults/operating_system.rb:8:in `block in <top (required)>'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/installer.rb:244:in `call'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/installer.rb:244:in `block in run_pre_install_hooks'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/installer.rb:243:in `each'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/installer.rb:243:in `run_pre_install_hooks'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/installer.rb:209:in `install'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/dependency_installer.rb:372:in `block in install'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/dependency_installer.rb:332:in `each'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/dependency_installer.rb:332:in `each_with_index'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems/dependency_installer.rb:332:in `install'
C:/Program Files (x86)/SketchUp/SketchUp 2014/Tools/RubyStdLib/rubygems.rb:526:in `install'
<main>:in `<main>'
-e:1:in `eval'
nil
Gem.platforms
["ruby", #<Gem::Platform:0x6b06430 @cpu="x86", @os="mingw32", @version=nil>]
Gem::install 'mysql'
[#<Gem::Specification:0x6b3a948 mysql-2.9.1-x86-mingw32>]

For some reason the change has to happen sooner than the preinstall_hooks for rubygems to “see” the modified local platform.

Can’t you change platforms.rb to correctly detect cpu/os and set the vars?

Any idea how that is done? We don’t develop the Ruby interpreter, just try to hook it up to the host application - and the documentation for that is non-existent. That makes every issue like this a major time sink where we don’t have clear guidance to how it can be solved. All of this is unknown territory.

I’m far from advanced in Ruby but after a lot of searching I found it. The Gem::ConfigMap has an incorrect value for arch ("platform_specific" in the 32-bit SU version I have checked, might be for all 32-bit versions not sure) so everything that happens below it in platforms.rb is like it didn’t exist. ConfigMap takes its values from RbConfig::CONFIG

  unless defined?(ConfigMap)
    ##
    # Configuration settings from ::RbConfig
    ConfigMap = Hash.new do |cm, key|
      cm[key] = RbConfig::CONFIG[key.to_s]
    end
  else
    RbConfigPriorities.each do |key|
      ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
    end
  end

the above is from compatibility.rb. Now the actual values that need to be changed reside in rbconfig.rb, and they are

  CONFIG["sitearch"] = "platform_specific"
  CONFIG["arch"] = "platform_specific"

sitearch doesnt have to be changed, it works even if it stays as it is, but the correct value is "i386-msvcrt". arch can be either "i386-mingw32" or "x86-mingw32", both work fine.

Gem.install 'mysql2'
[#<Gem::Specification:0x4954e6c mysql2-0.3.18-x86-mingw32>]
require 'mysql2'
DL is deprecated, please use Fiddle
true
client = Mysql2::Client.new(:host => "db4free.net", :username => "sepultribe", :password => "123456", :database => "testmysql2gem")
#<Mysql2::Client:0xb1a12f8 @read_timeout=nil, @query_options={:as=>:hash, :async=>false, :cast_booleans=>false, :symbolize_keys=>false, :database_timezone=>:local, :application_timezone=>nil, :cache_rows=>true, :connect_flags=>-2147442171, :cast=>true, :default_file=>nil, :default_group=>nil, :host=>"db4free.net", :username=>"sepultribe", :password=>"123456", :database=>"testmysql2gem"}>
results = client.query("SELECT COUNT(*) col FROM dual WHERE 1=0")
#<Mysql2::Result:0xb1a9f80 @query_options={:as=>:hash, :async=>false, :cast_booleans=>false, :symbolize_keys=>false, :database_timezone=>:local, :application_timezone=>nil, :cache_rows=>true, :connect_flags=>-2147442171, :cast=>true, :default_file=>nil, :default_group=>nil, :host=>"db4free.net", :username=>"sepultribe", :password=>"123456", :database=>"testmysql2gem"}>
results.each do |row|
  # conveniently, row is a hash
  # the keys are the fields, as you'd expect
  # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL
  puts row["id"] # row["id"].class == Fixnum
  if row["dne"]  # non-existant hash entry is nil
    puts row["dne"]
  end
end
[{"col"=>0}]

from rbconfig.rb

# This file was created by mkconfig.rb when ruby was built.

so something goes wrong when u build ruby for 32-bit sketchups and this file gets the wrong values in those spots.

Not at this time. Basically it will be overriding selected methods from
Tools/RubyStdLib/rubygems.rb
and
Tools/RubyStdLib/rubygems/defaults.rb

Read the notes at the top of:
Tools/RubyStdLib/rubygems.rb
Notice at the top of this loader file:

require 'rbconfig'

module Gem
  VERSION = '2.0.3'
end

# Must be first since it unloads the prelude from 1.9.2
require 'rubygems/compatibility'

require 'rubygems/defaults'
require 'rubygems/deprecate'
require 'rubygems/errors'

… then comes the important notes you should read, … then it defines module constants, arrays and module methods, some which likely need to be overridden,… then at the end of the loader file:

require 'rubygems/exceptions'

# REFACTOR: This should be pulled out into some kind of hacks file.
gem_preluded = Gem::GEM_PRELUDE_SUCKAGE and defined? Gem
unless gem_preluded then # TODO: remove guard after 1.9.2 dropped
  begin
    ##
    # Defaults the operating system (or packager) wants to provide for RubyGems.

    require 'rubygems/defaults/operating_system'
  rescue LoadError
  end

  if defined?(RUBY_ENGINE) then
    begin
      ##
      # Defaults the ruby implementation wants to provide for RubyGems

      require "rubygems/defaults/#{RUBY_ENGINE}"
    rescue LoadError
    end
  end
end

Note that RUBY_ENGINE is defined as "ruby", but the implementers file "rubygems/defaults/ruby.rb" does not yet exist.
It is the last rubygems configuration file loaded, before the loader modifies the global Kernel.require method.


I had already logged a couple of the methods that needed to be fixed but I think those were fixed and were pointing to the gem path strings.

Others that might need to be overridden, are:
THESE ALL RETURN INCORRECT VALUES FOR THE SKETCHUP ENVIRONMENT

Gem::ruby (returns "/bin/ruby.exe" should it return the path to the loaded ruby interpreter ?)

Gem::config_file

Gem::default_bindir
Gem::default_dir
Gem::default_rubygems_dirs
Gem::user_dir
Gem::default_path
Gem::default_key_path
Gem::default_cert_path

and maybe:
Gem::win_platform?

Yea, this “something” is the tricky part to determine.

Maybe I can find some Ruby dev guru’s when I go to the Barcelona Ruby Conference later this year. Matz will be there - but I’m not sure if he’s that involved in the Windows builds. Maybesomeone related to the Ruby Windows Installer project.

Maybe some people from this forum could try and compile ruby the way u do to try and recreate the issue if u post the config file? Do u compile ruby on linux or on windows?

First choice is always Luis Lavena. (A smart cookie.)