3

Our team is working on a new application that we started with Rails 3.1 and Ruby 1.9.2 - and last night I took it to Ruby 1.9.3.

One of the gems we were using in the dependency chain (css_parser) ended up having a require 'iconv' in it, triggering a deprecation warning in 1.9.3 that looked like this:

.../gems/activesupport-3.1.1/lib/active_support/dependencies.rb:240:in `block in require': iconv will be deprecated in the future, use String#encode instead.

At first I naively blamed that on rails without a better trace, until I didn't find a require 'iconv' in it anywhere.

The only way I tracked this down was that I started commenting things out in my Gemfile and then I finally got the bright idea to load irb and start requiring each library in turn. I also could have just done a filesystem grep in the gems directory, but I wasn't exactly sure that "require 'iconv'" was what was triggering the error.

What a PITA. There has to be a better way - just doing a --trace in rake tasks that load rails didn't cut it. Is there some way / any way of triggering a trace on this that would have shown me which line in the relatively long list of library dependencies was triggering the deprecation?

jay
  • 193
  • 8

2 Answers2

6

So, it's probably a little moot because I'm not likely to ever run into the problem again (and the css_parser gem was the only iconv-requiring gems in my current Rails 3.1/Ruby 1.9.3 projects).

But it was a puzzle, so I wanted to find some way of solving it.

The problem is very iconv-specific in this case. There are other ruby deprecations, but for the most part they seem to go through Kernel#warn (if ruby) or rb_warn() (if C) - but the warning in iconv.c is a little different than the others - at any rate it's a puts to rb_stderr.

So maybe I can do the following

  1. Override Kernel#require to capture stderr
  2. Check for an iconv message after calling the original Kernel#require
  3. Raise an exception if the message found, thereby getting a trace
  4. Do this before bundler runs if at all possible.

It turns out I can't do #4 - because Bundler calls Kernel.require directly - but I can use Bundler to parse the Gemfile to give me a list of things to require myself.

So this is what I get - thanks to this stack overflow post for a pointer on capturing standard error - and the rubygems source for the idea on aliasing the original Kernel#require

# override Kernel#require to intercept stderr messages on require
# and raise a custom error if we find one mentioning 'iconv'
require "stringio"

class RequireError < StandardError
end

module Kernel

  alias :the_original_require require
  private :the_original_require

  def capture_stderr
    # The output stream must be an IO-like object. In this case we capture it in
    # an in-memory IO object so we can return the string value. You can assign any
    # IO object here.
    previous_stderr, $stderr = $stderr, StringIO.new
    yield
    $stderr.string
  ensure
    # Restore the previous value of stderr (typically equal to STDERR).
    $stderr = previous_stderr
  end

  def require(name)
    captured_output = capture_stderr do
      the_original_require(name)
    end

    if(captured_output =~ %r{iconv})
      raise RequireError, 'iconv requirement found'
    end
  end
end

require 'bundler'

# load the list of Bundler requirements from the Gemfile
required_libraries = Bundler.definition.dependencies.map(&:name)

# loop through and require each, ignoring all errors other than
# our custom error

required_libraries.each do |requirement|
  begin
    require(requirement)
  rescue Exception => e
    if(e.class == RequireError)
      raise e
    end
  end
end

And voila! A trace message that helps track down where the iconv requirement was.

In the end, probably just a search for "require 'iconv'" is still best (once it's clear that's the what was causing the error).

But, as in life. Some Yaks Must Be Shaved.

Community
  • 1
  • 1
jay
  • 193
  • 8
  • You may be interested in http://stackoverflow.com/questions/660737/can-you-ask-ruby-to-treat-warnings-as-errors , which asks how to raise an exception when a warning is supplied. – Andrew Grimm Nov 06 '11 at 22:31
  • Yeah, I started out with the idea of overriding Kernel#warn and was quickly disabused of that notion when I started search the ruby code for the iconv error. I do wish there was a better way. – jay Nov 07 '11 at 13:47
0

You could take a look at the Gemfile.lock file, which holds all dependencies in a hierarchical tree and indicates the versions required by each gem. This might help to identify the gem that is requiring it.

murphyslaw
  • 640
  • 5
  • 12
  • That turned out to be helpful to narrow it down to the sub-dependencies as I commented out (or required) each of the named libraries in the Gemfile - but it'd still be nice to get a trace of the specific source line triggering the deprecation. – jay Oct 31 '11 at 19:00
  • True. I personally don't know any method to do this. Waiting for the answer as well. ;) – murphyslaw Oct 31 '11 at 19:21