Handling library dependencies in Ruby

From WhyNotWiki

Jump to: navigation, search

Contents

[edit] doing a plain require (not require_gem) triggers autorequire too

irb -> require 'rubygems'
    => true

irb -> require 'array/to_query_string'
LoadError: no such file to load -- lib/all.rb

I believe it was trying to load 'lib/all.rb' because that's what the specification.autorequire was and apparently the first time you load anything belonging to a gem, it will load the autorequire file for that gem...

[edit] What should I put for autorequire?

To answer that, first answer this: What files need to be / do you want to be loaded any time a user uses your gem (any part of the gem, even loading a specific file with require "filename"?).

Important: Make sure this file is within the s.require_path that you specified. So, you wouldn't want to do:

s.autorequire = "lib/init"
s.require_path = "lib"

Instead, do:

s.autorequire = "init"
s.require_path = "lib"

You probably don't want to specify a dir in the autorequire at all, since most of the time the file you'll want to auto-require will be in the root of lib anyway.

[edit] What does s.require_path do?

Just adds one or more subdirectories of your choice to Ruby's $LOAD_PATH.

s.require_path = "my_special_dir"

Causes:
/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/bin
/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/vendor
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/bin
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/bin
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/bin
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/my_special_dir       # <------------
/usr/lib/ruby/site_ruby/1.8
/usr/lib/ruby/site_ruby/1.8/i386-linux
/usr/lib/ruby/site_ruby
/usr/lib/site_ruby/1.8
/usr/lib/site_ruby/1.8/i386-linux
/usr/lib/site_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i386-linux
.

You can even do ".". Remember that any path you do will be relative to where your gem ends up being installed. So "." will actually refer to the base directory of your gem directory tree.

s.require_path = "."

/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/bin
/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/vendor
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/bin
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/bin
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/bin
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/.    # <--
/usr/lib/ruby/site_ruby/1.8
/usr/lib/ruby/site_ruby/1.8/i386-linux
/usr/lib/ruby/site_ruby
/usr/lib/site_ruby/1.8
/usr/lib/site_ruby/1.8/i386-linux
/usr/lib/site_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i386-linux
.

[edit] Relative paths in your gem source may be included relative to your working directory

I no longer think this is a problem. Just avoid it with proper load paths.

Here's an example. A true story that happened to me:

s.autorequire = "lib/all.rb"

all.rb:
puts "In all.rb."
puts "This is a local change that's only available in my development version, not in the installed gem!"
puts $LOAD_PATH

irb -> require 'rubygems'; require_gem 'TylerTest'
In all.rb.
This is a local change that's only available in my development version, not in the installed gem!
/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/bin
/usr/lib/ruby/gems/1.8/gems/facets-1.4.0/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/vendor
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/bin
/usr/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/bin
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/bin
/usr/lib/ruby/gems/1.8/gems/TylerTest-0.0.1/my_special_dir
/usr/lib/ruby/site_ruby/1.8
/usr/lib/ruby/site_ruby/1.8/i386-linux
/usr/lib/ruby/site_ruby
/usr/lib/site_ruby/1.8
/usr/lib/site_ruby/1.8/i386-linux
/usr/lib/site_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i386-linux
.
    => true

(specification.autorequire has to be specified as a relative path, by the way (because there's no way to know at the time the specification is created into which directory on the user's system it will be installed!). I'm just surprised that RubyGems actually loads it as a relative path. Since it knows which gem this is, it should know where that gem is installed on the system, and should look there only.)

Notice that "." is in the include path. So, since it didn't find it in any of the higher locations, it ended up loading ./lib/

Possible solutions:

  • use unique-ish names for your autorequire to reduce changes that it will load the wrong way?
  • ...

[edit] What's the difference between require 'gemname' and require_gem 'gemname'?

Apparently, require doesn't actually look at the gem name. It only looks for a matching file of the given name (looking in each of the gem's include paths?).

When you do require_gem, by contrast, it will find your gem if you specify it by name. Which files does it load then? I think it only loads the one specified by s.autorequire.

s.name    = "TylerTest"
s.autorequire = "all.rb"

irb -> require 'TylerTest'
LoadError: no such file to load -- TylerTest
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__'
        from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
        from (irb):3

irb -> require_gem 'TylerTest'
in all.rb!
    => true

[edit] Wish list / problems to solve

[edit] inclusion guards

Especially with rake tasks in files that get called more than once

One attempt at inclusion guards at the module level, using modules and a module variable (@@already_included):

module RailsSharedTasks
  mattr_accessor :already_included
  def self.already_included
    @@already_included ||= false
  end

  def self.included(base_module)
    return if @@already_included
    @@already_included = true
    include RailsSharedTasks::Common
  end
end # module RailsSharedTasks

include RailsSharedTasks

It tried to include the module every time the file was included. But the logic added to RailsSharedTasks.already_included made it so that the second time you included the module, it didn't do anything at all (well, it returns immediately).

[edit] Gem wish list

[edit] Why don't gems do auto-loading?

Why should I have to explicitly require "rake" in order to get FileList? Why can't the gem specification set it up so that trying to use the constant FileList will auto-load whatever files are needed fort that to work??

require "rubygems"
require "rake" # FileList

FileList[start_dir + "**/**/*.rb"].each do |filename|
...
Personal tools