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.
Native C Extension if Library is Available
The have_library
method has a return value:
Returns whether or not the given entry point
func
can be found withinlib
.
So you should be able to do this:
$defs.push('-DUSE_RUBY_UUID') if !have_library('uuid')
create_makefile("identifier")
And then set up your C to use libuuid if USE_RUBY_UUID
is not defined and call into the Ruby UUID library if it is defined.
Oddly enough, the have_header
and have_func
methods in mkmf.rb
add macros for you:
# File mkmf.rb, line 840
def have_header(header, preheaders = nil, &b)
checking_for header do
if try_header(cpp_include(preheaders)+cpp_include(header), &b)
$defs.push(format("-DHAVE_%s", header.tr_cpp))
true
else
false
end
end
end
but have_library
makes you do it yourself.
Making a Ruby project that is not a gem (or some other kind)
You have to actually try to build a gem so this is easy!
to use bundler without Rails, a gem, whatever just create a directory
mkdir my-non-gem-project
cd my-non-gem-project
install bundler
gem install bundler
and initialize your Gemfile
bundle init
that will create a Gemfile
for you and you can add to it and run bundle
to install the dependencies from it
The simplest way to use bundler in your project would then be to open your main app file and add
require 'bundler/setup'
Bundler.require
This will require all of the gems you have in your Gemfile in the file this is added to. I am pretty sure that this file must be in the same directory as your Gemfile. More information here
Have fun with your Ruby project!
In depth Ruby Gem development resources (book, video, sites)
Rubygems aren't related to distributed programming.
Can you please provide more details about what you're after, if you aren't asking a duplicate question? Related questions within Stack Overflow include:
- Gotchas for writing rubygems
- Ruby : How to write a gem ?
- What are the steps needed to create and publish a rubygem of your own?
- What is the modern way to structure a ruby gem?
(I know this is more of a comment than an answer, but it's too big to fit in the comments section)
which gems for asynhronous HTTP-request processing exists?
There is a good talk about it here (look down for the slides):
http://lanyrd.com/2012/rubyconf/szpth/
The person giving the talk also has a comparison of all the ruby http clients, which indicates their concurrency model:
http://bit.ly/RubyHTTPClients
Best ruby binding/gem for curl/libcurl
I highly recommend Typhoeus. It relies on lib-curl, and allows for all sorts of parallel and async possibilities. It offers ssl, stubbing, follows redirects, allows custom headers, true parallel requests for blazing speed, and generally has yet to let me down. Also, it is well maintained--at the moment, the last commit was 2 days ago!
Related Topics
When and Where Do I Require Files in a Rails Application
Ruby, Using Regex to Find Something in Between Two Strings
Is It a Bad Idea to Reload Routes Dynamically in Rails
What Is the Point of Object#Presence in Rails
Omniauth Facebook Expired Token Error
Running Phantomjs from a Ruby on Rails Application
How to Embed Dynamic Ruby Code to "Javascript" Section in Slim Templates
Rails: Difference Between Env.Fetch() and Env[]
Decoding Facebook's Signed Request in Ruby/Sinatra
How to Set a Request.Referrer Inside My Rspec
How to Pluck Email from Array of Users
Have Devise Create a Subdomain on Registration
What Rails Plugins Are Good, Stable and *Really* Enhance Your Code
Rails 4 Strong Parameters Without Required Parameters
Does Concurrency Happen Even When Only One Thread Is in a Thread Pool