How to use a local ruby gem inside a Sketchup Extension

I have developed a ruby gem that i want to use on my Sketchup extension. But, when i try to require it inside the extension, i a get a #<LoadError: cannot load such file – dpms-client>
/Applications/SketchUp 2018/SketchUp.app/
error.
I would like to know if there is a way to install my ruby gem either from the computer terminal, or inside the Sketchup ruby console, and require it inside the extension.

Is it a binary gem or a pure Ruby gem?

Is it released on RubyGems.org ?

It’s a pure Ruby gem and is not released on RubyGems.org .
P.S.I can’t release it because the code was written under the copyright of the company i work on. It is for private use.

In that case it should not be a gem. (A gem is for public use.)

You need to wrap it as a library within a submodule namespace of either your company’s top level namespace module or as a submodule of the extension submodule.

module SomeCompany

  module SomeLibrary # was a gem
    # library code
  end

  module SomePlugin

    include SomeLibrary

    # plugin code

  end

end

… OR …

module SomeCompany

  module SomePlugin

    module SomeLibrary # was a gem
      # library code
    end

    include SomeLibrary

    # plugin code

  end

end

You may divide up the code into separate files according to normal Ruby practice.

Thank you very much for replying Dan, i was thinking that this might be the right way to do it. But, just for the sake of knowing if it is possible, is there a way to install a local gem through the Sketchup’s ruby console? Or, to require a installed gem that is not on RubyGems.org on the Sketchup’s ruby console?

Console or code, … it doesn’t matter.

The Ruby API subcategory has numerous topic threads on gems under SketchUp …

IF (somehow) you copied a gem to the cache, then the Gem installer is supposed to use that instead of trying to download it from the gem server.

Gem::install("mygem")

The “somehow” would need to use FileUtils to copy the gem file to the cache folder.

(You might also need to provide a .gemspec file in the “specifications” folder, ie do whatever is usually done when the gem is downloaded from the gem server.)

GEM_CACHE = File.join( ENV['GEM_HOME'], 'cache' )
GEM_SPECS = File.join( ENV['GEM_HOME'], 'specifications' )

When SketchUp loads it adds the GEM_HOME and GEM_PATH variables to it’s copy of the environment.

IF the gem is installed into a sub-folder of SketchUp’s “gems” folder, then rubygems modification of Kernel.require is supposed to find and load it.

GEMS = File.join( ENV['GEM_HOME'], 'gems' )

from Thomas Thomassen on the SketchUp Development team …

1 Like

Where is your gem located and how are you trying to load it?

Add the directory your gem is located to the $LOAD_PATH global variable and see if you can load it then.

Installing private packages via a package manager is usually done by having a private repository (from where the package manager searches and downloads packages).
If your company does software development (more than this single extension/gem), you should consider setting up a private repository for development purposes. There are for example Gem In A Box, Sonatype Nexus, Ruby Artifactory, Gemfury.

Ruby Gems has a command to configure repositories:

gem sources --add <url>

If you want to load it from a directory (not using a repository), it probably should not be using a package manager (ruby gems) at all, and not be a gem.

You can however put it in the current directory where gem install will pick it up before querying the repository URL.

I have tried using both methods.
For the first one, i’ve moved the gem to the ‘cache’ folder and the .gemspec to the ‘specifications’ folder. I’m getting the error ‘Error: #<Gem::UnsatisfiableDependencyError: Unable to resolve dependency: user requested ‘dpms-client (= 0.1.0)’>’. I’m running the command ‘Gem::install(“dpms-client”, ‘0.1.0’)’ .
In second option, i’ve copied the folder that ruby created when i installed the gem on my computer( /Users/mateusgondim/.rvm/gems/ruby-2.5.0/gems/dpms-client) to the SketchUp’s /Users/mateusgondim/Library/Application Support/SketchUp 2018/SketchUp/Gems folder. When i run the command Kernel.require(“dpms-client”) i’m getting the error ‘Error: #<LoadError: cannot load such file – dpms-client>

Now you know why SketchUp plugin developers don’t use gems.

Anyway … try just … (this is what the rubygems modification of Kernel.require does.)

DPMS_PATH = File.join( ENV['GEM_HOME'], 'gems', 'dpms-client', 'lib' )
$LOAD_PATH << DPMS_PATH
require "dpms-client" # <--<< name of loader file inside "lib" subdir

rubygems ASSUMES that the loader file inside the “lib” subdir has the same name as the gem itself. Sometimes when it does not weird things happen. (An example is the rubyzip gem which has [or used to have] a loader named "lib/zip.rb" instead of "lib/rubyzip.rb".)

Hahahahaha, yeah man.
Now it finds the gem, but my gem requires the rest-client gem. So i’m getting the error ‘Error: #<LoadError: cannot load such file – rest-client>’. I’ve tried installing the rest-client using Gem::install(“rest-client”). But when i run that command i’m getting the error:

**Gem::install("rest-client")**
**Error: #<Gem::Ext::BuildError: ERROR: Failed to build gem native extension.**

**"/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/bin/ruby" -r ./siteconf20180912-28983-1e6dg1.rb extconf.rb**
**sh: /Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/bin/ruby: No such file or directory**

**extconf failed, exit code 127**

**Gem files will remain installed in /Users/mateusgondim/Library/Application Support/SketchUp 2018/SketchUp/Gems/gems/unf_ext-0.0.7.5 for inspection.**
**Results logged to /Users/mateusgondim/Library/Application Support/SketchUp 2018/SketchUp/Gems/extensions/x86_64-darwin-14/2.2.0/unf_ext-0.0.7.5/gem_make.out**

**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:89:in `run'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/ext_conf_builder.rb:36:in `block in build'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/tempfile.rb:319:in `open'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/ext_conf_builder.rb:19:in `build'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:161:in `block (2 levels) in build_extension'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:160:in `chdir'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:160:in `block in build_extension'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:159:in `synchronize'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:159:in `build_extension'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:198:in `block in build_extensions'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:195:in `each'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/ext/builder.rb:195:in `build_extensions'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/installer.rb:702:in `build_extensions'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/installer.rb:250:in `install'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/request_set.rb:166:in `block in install'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/request_set.rb:150:in `each'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/request_set.rb:150:in `install'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems/dependency_installer.rb:394:in `install'**
**/Applications/SketchUp 2018/SketchUp.app/Contents/Frameworks/Ruby.framework/Versions/2.2/lib/ruby/2.2.0/rubygems.rb:558:in `install'**
**<main>:in `<main>'**

Yeah… i’m really thinking i should just not use gems.

Yes, I refer you back to what Thomas said in the quote above.

SketchUp does not support binary gems that need compiling “on the fly”.

You’d need to have DevKit installed, and the exact Ruby version that SketchUp is using.
(Which is 2.2.4 under SU2017 and 2018)

Yeah, even if i make my gem a library submodule of my extension, i still need to use the ‘rest-client’ gem, so i need to install it someway…

How can i install ‘rest-client’ on OSX? DevKit is only for Windows right?

Ruby is rather “native” on Unix, like macOS. The purpose of DevKit is to make the required Unix build tools also available for Windows.

On macOS the build tools can be installed with coreutils. Setting up your own Ruby Dev Environment on a Mac · GitHub

1 Like

If you need to use a gem with your extension I really recommend that you make a copy of your own and wrap it into your extension namespace. Otherwise you’re likely to run into clashes with other extensions that would try to use the same gem.

I’ve done this myself before. It’s a bit clunky at times. You’d need a version of each target Ruby versions installed as a standalone installation on your system. For Window you need DevKit installed. Then install the gem to your standalone Ruby installation. Afterwards you can find it in that Ruby’s gem directory where you can copy it into your project and made modifications so that’s it’s wrapped in your own namespace.

For Mac it’d be the same, except as you say RubyInstaller is only for Windows. Mac comes with a compiler out of the box so you might not need anything extra. However, to get the right Ruby versions I recommend you use RVM to manage your Ruby installations.

Depending on what you need, you might find that it’s easier to use a C/C++ library and instead write your own Ruby C Extension wrapping that library.

1 Like

Yeah, it seams really clunky, because if i understood correctly, if the rest-client gem requires other gems that are compiled ‘on the fly’, i would need to bundle each one of them together with my extension. That would make my extension extremely bloated, just because SketchUp cannot simply install them from the console and allow me to require them.

Yea, the gem system isn’t very useful in SketchUp.

Gems were designed to be used/installed by the developer on a server they control for a single app. In SketchUp it would happen on a client’s machine, in an environment that is shared among many apps (extensions). So “normal” Ruby workflow (webdev workflow) doesn’t always apply.

You mentioned that this is in a company environment. Oddly enough, I thought issues like this might happen, and that it could be better supported in SU.

Re a local gem file, from stand-alone Ruby, they can be installed with gem install <gem file path> -l.

But, you mentioned that your gem has a dependency on rest-client. You could probably check the git repo, but on the RubyGems page, you’ll see versions like x64-mingw32, which is a suffix used by gems that are pre-compiled for Windows. IOW, the gem has c source code along with Ruby code.

The tools for installing/building/compiling those gems are CLI apps (command line interface), designed to be used from a command line, which doesn’t exist in SU.

Your only option is to hook SU so it can access gems installed in RubyGems --user-install folder, then install rest-client there with a standalone version of Ruby.

I’ve done it on Windows, MacOS should work the same. It isn’t rocket science, but it also isn’t trivial.

Re gems and SU, the main issue is namespace collisions. If I installed some-gem v1.0, and you installed some-gem v2.0, there is no way for both to be loaded at the same time.

There has been some discussion in Ruby about the issue, but it doesn’t affect most apps.

Thanks for the reply Greg. I think the main problem with these kind of solution is that, it’s overly complicated to the normal user, i.e, the extension i’m building will be used by designers that don’t necessarily know how ruby works. So basically everybody that uses the extension would need to install those gems separately and hook it up with SU, which is not simple for someone with little to no knowledge of Ruby and gems. A SketchUp extension should be 100% straightforward to a designer that does not know about Ruby.
What i’ve decided to do is to remove the rest-client dependency, i.e, i’m writing the http request library from scratch, and i’m going to change the Gem to be a submodule of my extension. It means more work, but at least it will remove any external dependencies, thankfully the project was still on its early stages, so it only had this external gem dependency.

1 Like

Yea, we did the same thing for Trimble Connect. Using just the basic Net::Http stdlib in Ruby. Meant we needed to implement some of the higher level logic, but it was the most reasonable way given the gem situation. I think the latest version uses Sketchup::Http now, which avoids some of the HTTPS issues we had with Ruby over the years.