Find out which gems require native c extensions from a Gemfile? - ruby

I just recently started shifting attention towards deploying Ruby apps atop TorqueBox which of course is built atop Jruby. Hitherto I have been basically performing a bundle install and then tackling each gem along the way to jrubydom, but I've hit a couple gems that have taken me some considerable time to resolve due to needing to reimplement large portions of them.
Is there a way to invoke bundler or rubygems to run through all gems and their deps to test if they require native c extensions and then return such a list? It sure would be nice to tackle some of the more minor items or even to know if it is worthwhile to tackle a project in terms of moving it to jruby.

Based on the fact that gems with native extensions usually have an /ext directory, I made a simple oneliner that finds these gems:
puts `bundle show --paths`.split("\n").select{|dep| File.directory?("#{dep}/ext") }.map{|dep| dep.split('/').last }.join(', ')
You can do this on the command line with this command:
$ bundle show --paths | ruby -e "STDIN.each_line {|dep| puts dep.split('/').last if File.directory?(File.join(dep.chomp, 'ext')) }"

You can use JRuby Lint for that. It will will check for some gems requiring C extension and even list alternative (based on this list).

Related

How do I get rrdtool from homebrew to work with ruby on macOS

In our Rails application we do require 'RRD' at some point, but that results in a cannot load such file -- RRD. So obviously I used homebrew to install rrdtool, but the error remains.
The docs at https://oss.oetiker.ch/rrdtool/prog/rrdruby.en.html provide two options:
Either:
$: << '/path/to/rrdtool/lib/ruby/1.8/i386-linux'
require "RRD"
In my /opt/homebrew/Cellar/rrdtool/1.8.0/lib directory there's no mention of ruby, which is because of the --disable-ruby-site-install flag in the formula, because when I skip that flag I do actually get something: /opt/homebrew/Cellar/rrdtool/1.8.0/lib/ruby/2.6.0/universal-darwin21. However replacing the path/to string with this path still gives the error.
Or:
If you use the --ruby-site-install configure option you can drop the $: line since the RRDtool module will be found automatically.
Which is a little confusing (and probably outdated) because here it seems that ruby site install is disabled by default and you have to enable it proactively, whereas in the formula it's actually actively disabled.
Either way: both options didn't do the trick for me and if there's a solution without homebrew that's also fine.
For good measure: I'm on macOS Monterey
TL;DR
For the most part, I'd say that using a non-standard gem without a Ruby version manager is your main issue. There are instructions on the rrdruby site for installing it, but they don't follow typical conventions, so your mileage will vary.
Some Practical Suggestions
The require keyword is for gems, not binaries. You need to have an rrdtool-related gem installed, available to your Ruby instance (usually through a Bundler Gemfile or gemspec, or via the RUBYOPTS environment variable or your in-process Ruby $LOAD_PATH), and then require the correct name of the gem in your code. For example, using the older rrd-ffi gem:
# use sudo if you're installing it to the system,
# but I would strongly recommend a ruby version
# manager instead
gem install rrd-ffi
# in your Ruby class/module file
require "rrd"
For the gem you seem to be using, you have to compile the gem first to make it usable, and then ensure it's available in your Ruby $LOAD_PATH (or other gem lookup mechanism) before trying to require it. The error message you're seeing is basically telling you that a gem with that name is not available as called within any of the standard lookup locations.
Again, I'd suggest reading the build documentation for your gem, and then seeing if you can install it as part of a Bundler bundle, RVM gemset, or other non-system approach if you can. Otherwise, follow the directions for the rrdruby tool, which is not available as a standard Rubygems.org gem, in order to make it available before trying to require it.
Beware of Outdated or Non-Standard Gems
Most of the RRD gems I found were quite old; most were 7-8 years old or older, so their compatibility with current Rubies is potentially suspect. The gem-builder you're using is newer, but doesn't seem to be designed as a standard gem, so you need to build it and install it in a suitable lookup path before it can be required. Installing gems as system gems is almost always a bad idea, so I'd strongly recommend building it from source and using a ruby version manager rather than following the rrdtool author's atypical suggestions. YMMV.

Why use "bundle --deployment" instead of "bundle --without"?

Background: Not the Question
I have a project that is running in both development and production in a chruby environment, where ruby-install was installed as root and rubies are stored in /opt/rubies. I have a really hard time (as many people do) getting nokogiri to compile its native extensions as part of a bundle, but it compiles fine as any chruby user outside of the project directory and so long as there are no binstubs. That's just background context; it's not really what my question is about.
My Current Hack
I find that when I have committed binstubs to my project, all kinds of badness happens. So far, my best solution seems to be:
# skip any bundled/binstub version of bundler
rm -rf ./bin
`which bundle` install --binstubs --without="development test"
which seems to work just fine. Nokogiri is apparently not a production dependency (yay!) and the ./bin directory gets trimmed down to just the gems needed for production.
Question: Would the Bundler Deployment Flag Fix Anything?
So, here's the question: What's the difference between what I'm doing here, and bundle install --deploy with or without binstubs? I know it points to vendor/bundle instead of bin by default, but the docs don't really explain the pros and cons of this approach (at least not in a way that I'm understanding).
Aside from knowledge, which is valuable for its own sake, I'd really like to vendor in production gems in a way that it's likely to work across systems (e.g. RHEL6 and RHEL7) without having to rebuild native extensions or strip out development/test gems on production machines.
Is the deployment flag the answer? Or is there a better way to vendor gems with native extensions for cross-distro projects?

A variation on internally distributing a rails app

I have written a ruby service that I want to package and distribute internally to a specific environment (a standardized linux host). After digging around a bit for the best ways to create a distribution, I've come across a lot of blogs and answers here that recommend bundler and gem packaging as well as a lot of binary distribution options (e.g. traveling-ruby), but these all seem like a bit too much for a relatively simple service that will get deployed to a known environment. I want to create a distribution that doesn't require dependencies to be resolved at deploy time (e.g. bundle install --deployment is not the approach I want).
So with this in mind, is there an available framework that is commonly in use by ruby apps that would create a redistributable package with all dependencies included in it? I am currently doing this in 2 steps and wondering if there's a "better" ruby-way of doing it - something along the lines of gem build ... that creates dependency-inclusive archive?
# Assuming `bundle install` was run on developer workstation and there's a `Gemfile.lock`
$ bundle install --deployment
$ tar zcf ../my-app.tar.gz ./
my-app.tar.gz can now be distributed and if I have an executable to run it with, I can do so with bundle exec bin/run from within the directory after it's extracted. This a good approach?
I've seen a gem called crate that might be able to work....

How to customize Gemfile per developer?

There is a common pattern:
there are many developers working on one project and the Gemfile(.lock) is shared via SCM. But what if some developers want to use different tools for testing and development? How to do it?
The problem is, that when you put conditional sections to your Gemfile, also the Gemfile.lock will be different for each developer and therefor you'll get conflict each time you commit to SCM.
Is there some simple, widely acknowledged solution?
I like to have this in my Gemfile:
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local"
if File.file?(local_gemfile)
require local_gemfile
end
I also have Gemfile.local and Gemfile.lock in gitignore. I know I'm not "supposed to", but I don't think the caveats (such as the ones you mention in your question) are worth it.
UPDATE for Bundler 1.0.10 as of March 3, 2011
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local.rb"
if File.file?(local_gemfile)
self.instance_eval(Bundler.read_file(local_gemfile))
end
I had to use this with Rails 3 and Bundler 1.0.10.
If you check in something that depends on a gem that gem should be in the gemfile. If the code in the repository does not depend on a gem, there's no need to have it in the gemfile. So, unless your developers don't check in their tests (which would be weird) you would need all the test's dependencies if you want to run the whole tests suite anyway.
If the gems aren't necessary to run the app or its tests the gems don't need to be in the gemfile. Just have each developer create a gemset (I assume you're using RVM, if you don't you should) for the app and install whatever they need there, and then just add what the app needs to run to the gemfile.
You can use Bundler's without flag to exclude groups.
If you have the following Gemfile
group :jakubs_testing_tools do
gem "rspec"
gem "faker"
end
You can exclude them with bundle install
$ bundle install --without jakubs_testing_tools
http://gembundler.com/groups.html
It won't help you right now, but there's been an open feature request for Bundler to add support for a Gemfile.local for ages. It is planned for somewhere in the 1.x series, so stay tuned.
If your main problem is developer-specific IRB gems, there are a couple of workarounds in the issue's comments.
I suppose the install_if method (added recently) solves the problem:
install_if -> { `whoami`.strip == 'jakub' } do
gem "pry-rails"
end
See http://bundler.io/v1.14/man/gemfile.5.html#INSTALL_IF
Each developer can create their own branch - Bundler works fine with different branches having different Gemfile contents. It's a good idea for developers to prefix branch names with their initials, to avoid confusion or collisions.
(basically the same idea as in August Lilleaas's comment: gitignore)
Put the default/minimal Gemfile in SCM and then have developers change it on their systems and never commit. Have them add it to their inactive changeset in their SVN client (if they use one), other SCMs should have something similar.
This is how we do this in my company - works really well :)

gem command . What does that mean

Sometimes I have seen following code.
gem 'factory_girl','= 1.2.3'
require 'factory_girl'
I tried to look at gem doc but could not find answer to the question of what does the first line do in code above?
What you're looking for in the gem docs is about Coding with Rubygems.
The first line basically says "Hey, go get this gem with this version" from the install directory for gems and load it into the environment. This is mainly to help you add version dependencies to your requires instead of just doing require 'factory_girl' by itself.
Edit: To add on to Jörg's point below, I thought that Ryan Tomayko had a pretty good short and sweet article about why doing this is "wrong".
As #theIV already explained, this activates the factory_girl gem, using exactly (because of the = sign) version 1.2.3.
Note, however, that this is very bad practice and should never be done. If you activate gems manually inside your code, it means that people who do not use RubyGems can no longer use your code.
RubyGems is a package manager. Your code should never care about what package manager was used to install it. Some people prefer RubyGems, some dpkg/APT, some RPM/YUM, some RPM/APT, some RPM/URPMI, some RPM/YaST2, Portage, FreeBSD ports, pkgsrc, MacPorts, slashpackage, CoAPP, Conary, Slackware. There's tons of them. Some people like not to use any package manager at all. Or, they use RubyGems just for downloading, but then unpack the gem into their vendor directory.
All of this cannot possibly work, if you use the gem method in your code.

Resources