MRI/JRuby Bundler Gemspec/Gemfile Problem

Hi, everyone.

I’m writing a gem that can be used from both MRI Ruby and JRuby to talk
to
a Java library in a jar file. In JRuby talking to the Java library
requires no special configuration, but from MRI Ruby I need to use the
rjb
(Ruby Java Bridge) gem.
So, I want to install the rjb gem if in MRI Ruby, bot want not to
install
it if in JRuby, since it requires some native building.
I have this in my gemspec, which works fine:
unless /java/ === RUBY_PLATFORM spec.add_dependency ‘rjb’ end
However, when I run bundle in MRI, the resulting Gemfile.lock contains
the
rjb dependency without any condition not to use it in JRuby:
… rake (10.0.4) rjb (1.4.6) rspec (2.13.0) … So if I build
the
gem, distribute it, and then gem install it in JRuby, the gem install
tries
to build rjb, and fails because of the native extensions.
What’s the best way to deal with this? Should I duplicate the gem
dependencies, putting them in the Gemfile instead of the ‘gemspec’
that’s
there now? Or build two different gems (I hope not)?
Thanks,Keith

Hi Keith,

You do need to create a separate gem for the different platforms –
thankfully, rake-compiler (GitHub - rake-compiler/rake-compiler: Provide a standard and simplified way to build and package Ruby C and Java extensions using Rake as glue.)
makes it easy to do so.
I had described how over at SO
(ruby - How do I make a gem that targets both MRI and JRuby? - Stack Overflow), but I should probably
stick that somewhere in the wiki too.

Regards,
Sébastien.

Le mer. 12 juin 2013 19:26:12 IST, Keith B. a écrit :

Sebastien -

I’ve been trying to get your instructions to work but have been unable.
I’ve tried the following:

rake native gem # in MRI
…Don’t know how to build task ‘ext/my_gem_name/extconf.rb’…

rake java gem # in JRuby
…javac: no source files…

rake gem
…Don’t know how to build task ‘gem’…

I gem installed rake-compiler. Is it not there?

In my case, I don’t need to compile anything. The only difference is
that MRI has a dependency that JRuby does not (RJB). What am I doing
wrong?

  • Keith

Hi Keith,

As you mentioned, you have the following in your gemspec:

unless /java/ === RUBY_PLATFORM spec.add_dependency ‘rjb’ end

The gemspec file is the description of how to package your gem, so the
RUBY_PLATFORM here will be the one of the ruby you use to build/package
your gem, not the one you

Rhett and Sebastien -

Maybe I’m overthinking this…my code doesn’t contain any extensions at
all, I just have a dependency on a gem on one platform but not the
other.
I don’t think I need rake-compile.

If I have to create one gem per platform, then I guess all I need to do
is
something like this:

rvm 1.9
./build_gem.sh

rvm jruby
./build_gem.sh

…where build_gem.sh is the shell script I posted at
Shows a strategy for creating both MRI and JRuby versions of a gem. · GitHub.

My gemspec file has this:

is_jruby = /java/ === RUBY_PLATFORM
spec.version = is_jruby ? CaptiveValidator::VERSION + ‘.java’ :
CaptiveValidator::VERSION

I’ve seen other gems use an underscore instead (_java) (yours included,
Rhett), but when I try specifying that in the gemspec I get an error
from
the gem build:

Malformed version number string 0.1.0_java

Rhett, how did you do it? Did you rename the file manually? I could
certainly do that, but I assume if the gem build forbids it there is a
good
reason…or no?

Thanks,
Keith

Hi Keith,

I am not familiar with rake-compiler and I’m not sure it will help you
in this case, but I can give you an overview of what you need to
achieve.

When you have a gem that has different dependencies on different rubies,
you need to package the gem separately for each platform. Adding a
condition on the dependency in the gemspec is not sufficient; you
actually need to build and publish variants of the gem with different
values in the gemspec’s “platform” attribute. (This is because the
gemspec that is packaged in the gem isn’t the literal code you author in
the gemspec file – it’s a serialized representation derived from
interpreting the gemspec in the interpreter on which the gem is being
packaged. Any conditions will be evaluated in the published gemspec.)

One of my gems, ladle1, has both JRuby and generic ruby versions. You
can take a look at its source[2] to see how this happens, but basically
I use RVM to package it separately on both MRI and JRuby and then
publish both.

I believe if you do handle the packaging like this, bundler will do the
right thing. I haven’t tried it though, so I don’t know for sure.

Rhett

[2]: GitHub - NUBIC/ladle: Ladle dishes out steaming helpings of lightweight directory access (LDAP) for use in testing with rspec, cucumber, or any other ruby test framework. ; ladle.gemspec and meta.rakefile
are the key files

Rhett -

Thanks, I realized that shortly after writing you, and was composing
another message when you responded.

I’m now doing the same thing with another gem, and gem build appends
“.java” to the gem file name instead of “-java”. Any idea why? The
“.java” is interpreted by Gem in a Box as meaning it’s a prerelease
version
(a gem convention I think), but that was not my intention.

Also, given that the Gemfile.lock file will differ in the respective
platform, how do you deal with committing it to the version control
repo?

  • Keith

Sorry, I could swear I checked the gemspec and version files, but I had
made the .java stuff happen myself.

I’m still perplexed as to how to handle committing the Gemfile.lock,
given
that it will differ for the two platforms.

  • Keith

Hi Keith,

I’ve seen other gems use an underscore instead (_java) (yours included, Rhett),
but when I try specifying that in the gemspec I get an error from the gem build:

Malformed version number string 0.1.0_java

Rhett, how did you do it? Did you rename the file manually? I could certainly
do that, but I assume if the gem build forbids it there is a good reason…or no?

You don’t want to change the version number. You want to change
spec.platform if it is anything other than “ruby”, rubygems will add
an appropriate platform suffix to the gem package name. The platform
attribute is also what rubygems (and hopefully bundler) will use to pick
the right gem to install on MRI vs. JRuby.

Rhett

Hi,

I’m still perplexed as to how to handle committing the Gemfile.lock, given that
it will differ for the two platforms.

I don’t have experience doing this with a real application, but I just
tried a trivial test app and it seemed to work. I created a Gemfile with
a gem that had both generic and java versions. Then I did bundle install on MRI, which created a Gemfile.lock. And then I did bundle install on JRuby, which updated Gemfile.lock so that it included the
java-platform dependencies also. Then I switched back to MRI and did
bundle install a third time; the java platform information in
Gemfile.lock was preserved.

So, try it and see.

Rhett

I think the recommendation when developing a gem is to not check your
Gemfile.lock into source control so it would just be generated for each
platform you create the gem for.

When developing a gem, use the
gemspechttp://gembundler.com/rubygems.html method
in your Gemfile to avoid duplication. In general, a gem’s Gemfile should
contain the Rubygems source and a single gemspec line. Do not check
your
Gemfile.lock into version control
, since it enforces precision that
does
not exist in the gem command, which is used to install gems in practice.
Even if the precision could be enforced, you wouldn’t want it, since it
would prevent people from using your library with versions of its
dependencies that are different from the ones you used to develop the
gem.”