Approach for Installing System Service Implemented as Ruby Gem

Approach for installing system service implemented as Ruby gem

You can do it. However it is probably not quite the recommended approach. But yes it is possible to run arbitary code during gem installation using the extensions option.

From the RubyGems Manual:

Usage
spec.extensions << 'ext/rmagic/extconf.rb'

Notes
These files will be run when the gem is installed, causing the
C (or whatever) code to be compiled on the user’s machine.

Just place whatever ruby code you need into the extconf.rb (or equivalent) file.

Examples for building C-extensions from the RubyGems Guides:

http://guides.rubygems.org/c-extensions/

Approach for installing system service implemented as Ruby gem

You can do it. However it is probably not quite the recommended approach. But yes it is possible to run arbitary code during gem installation using the extensions option.

From the RubyGems Manual:

Usage
spec.extensions << 'ext/rmagic/extconf.rb'

Notes
These files will be run when the gem is installed, causing the
C (or whatever) code to be compiled on the user’s machine.

Just place whatever ruby code you need into the extconf.rb (or equivalent) file.

Examples for building C-extensions from the RubyGems Guides:

http://guides.rubygems.org/c-extensions/

Can't install gems on OS X El Capitan

Disclaimer: @theTinMan and other Ruby developers often point out not to use sudo when installing gems and point to things like RVM. That's absolutely true when doing Ruby development. Go ahead and use that.

However, many of us just want some binary that happens to be distributed as a gem (e.g. fakes3, cocoapods, xcpretty …). I definitely don't want to bother with managing a separate ruby. Here are your quicker options:

Option 1: Keep using sudo

Using sudo is probably fine if you want these tools to be installed globally.

The problem is that these binaries are installed into /usr/bin, which is off-limits since El Capitan. However, you can install them into /usr/local/bin instead. That's where Homebrew install its stuff, so it probably exists already.

sudo gem install fakes3 -n/usr/local/bin

Gems will be installed into /usr/local/bin and every user on your system can use them if it's in their PATH.

Option 2: Install in your home directory (without sudo)

The following will install gems in ~/.gem and put binaries in ~/bin (which you should then add to your PATH).

gem install fakes3 --user-install -n~/bin

Make it the default

Either way, you can add these parameters to your ~/.gemrc so you don't have to remember them:

gem: -n/usr/local/bin

i.e. echo "gem: -n/usr/local/bin" >> ~/.gemrc

or

gem: --user-install -n~/bin

i.e. echo "gem: --user-install -n~/bin" >> ~/.gemrc

(Tip: You can also throw in --no-document to skip generating Ruby developer documentation.)

gem cannot access rubygems.org

api.rubygems.org is currently experiencing issues with IPv6 setup: this hostname has 4 IPv6 addresses, but responds on neither of them. Neither to ping, nor to TCP connection attempts. When you are running gem, your gem tries IPv6 addresses first and times out on them, not having time to even try IPv4 addresses.

The solution is to lower priority of IPv6 addresses for api.rubygems.org, so that gem will try IPv4 addresses first. In order to do it, put these lines into /etc/gai.conf:


# Debian defaults.
precedence ::1/128 50
precedence ::/0 40
precedence 2002::/16 30
precedence ::/96 20
precedence ::ffff:0:0/96 10

# Low precedence for api.rubygems.org IPv6 addresses.
precedence 2a04:4e42::0/32 5

NoMethodError: private method `open' called for Gem::Package:Class An error occurred while installing rake (10.0.3), and Bundler cannot continue

You should first update Rubygems:

gem update --system

And then update Bundler:

gem install bundler

Does Rails need to be installed as a system gem?

It totally makes sense to NOT install rails as system gem.

Without messing up rbenv or other ruby version manager you use, below are brief steps to create (initialize) a new Rails app from a directory with a Gemfile:

mkdir rails_app
cd rails_app
vi Gemfile # Edit it to include a rails version you need
bundle --path vendor # Wait for bundler to finish
bundle exec rails new ./

The last step would ask: Overwrite /path/to/rails_app/Gemfile? (enter "h" for help) [Ynaqdh]. Input y to get the default Rails Gemfile content.

Note: the above steps specify the local vendor directory (inside the rails app folder) to avoid installing gems to system global scope.

Native extensions fallback to pure Ruby if not supported on gem install

This is my best result attempting to answer my own question to date. It appears to work for JRuby (tested in Travis and on my local installation under RVM), which was my main goal. However, I would be very interested in confirmations of it working in other environments, and for any input on how to make it more generic and/or robust:


The gem installation code expects a Makefile as output from extconf.rb, but has no opinion on what that should contain. Therefore extconf.rb can decide to create a do nothing Makefile, instead of calling create_makefile from mkmf. In practice that might look like this:

ext/foo/extconf.rb

can_compile_extensions = false
want_extensions = true

begin
require 'mkmf'
can_compile_extensions = true
rescue Exception
# This will appear only in verbose mode.
$stderr.puts "Could not require 'mkmf'. Not fatal, the extensions are optional."
end

if can_compile_extensions && want_extensions
create_makefile( 'foo/foo' )

else
# Create a dummy Makefile, to satisfy Gem::Installer#install
mfile = open("Makefile", "wb")
mfile.puts '.PHONY: install'
mfile.puts 'install:'
mfile.puts "\t" + '@echo "Extensions not installed, falling back to pure Ruby version."'
mfile.close

end

As suggested in the question, this answer also requires the following logic to load the Ruby fallback code in the main library:

lib/foo.rb (excerpt)

begin
# Extension target, might not exist on some installations
require 'foo/foo'
rescue LoadError
# Pure Ruby fallback, should cover all methods that are otherwise in extension
require 'foo/foo_pure_ruby'
end

Following this route also requires some juggling of rake tasks, so that the default rake task doesn't attempt to compile on Rubies that we're testing on that don't have capability to compile extensions:

Rakefile (excerpts)

def can_compile_extensions
return false if RUBY_DESCRIPTION =~ /jruby/
return true
end

if can_compile_extensions
task :default => [:compile, :test]
else
task :default => [:test]
end

Note the Rakefile part doesn't have to be completely generic, it just has to cover known environments we want to locally build and test the gem on (e.g. all the Travis targets).

I have noticed one annoyance. That is by default you will see Ruby Gems' message Building native extensions. This could take a while..., and no indication that the extension compilation was skipped. However, if you invoke the installer with gem install foo --verbose you do see the messages added to extconf.rb, so it's not too bad.



Related Topics



Leave a reply



Submit