Module: FastRequire

Included in:
Kernel
Defined in:
lib/faster_require.rb

Constant Summary collapse

VERSION =
File.read(File.dirname(__FILE__) + "/../VERSION")

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.already_loadedObject



165
166
167
# File 'lib/faster_require.rb', line 165

def self.already_loaded
  @@already_loaded
end

.clear_all!Object

for testing use only, basically



194
195
196
197
198
199
200
201
202
203
204
# File 'lib/faster_require.rb', line 194

def self.clear_all!
  require 'fileutils'
  success = false
  if File.exist? @@dir
    FileUtils.rm_rf @@dir 
    success = true
  end
  @@require_locs.clear
  setup
  success
end

.default_saveObject



185
186
187
# File 'lib/faster_require.rb', line 185

def self.default_save
  self.save @@loc
end

.dirObject



173
174
175
# File 'lib/faster_require.rb', line 173

def self.dir
  @@dir
end

.guess_discover(partial_name, add_dot_rb = false) ⇒ Object

try to see where this file was loaded from, from $: partial_name might be abc.rb, or might be abc partial_name might be a full path, too



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/faster_require.rb', line 127

def self.guess_discover partial_name, add_dot_rb = false

  # test for full path first
  # unfortunately it has to be a full separate test
  # for windoze sake, as drive letter could be different than slapping a '/' on the dir to test list...
  tests = [partial_name]

  if add_dot_rb
    tests << partial_name + '.rb'
    tests << partial_name + '.' + RbConfig::CONFIG['DLEXT']
  end

  tests.each{|b|
    # assume that .rb.rb is...valid...?
    if File.file?(b) && ((b[-3..-1] == '.rb') || (b[-3..-1] == '.' + RbConfig::CONFIG['DLEXT']))
      return File.expand_path(b)
    end
  }

  for dir in $:
    if File.file?(b = (dir + '/' + partial_name))
      # make sure we require a file that has the right suffix...
      if (b[-3..-1] == '.rb')  || (b[-3..-1] == '.' + RbConfig::CONFIG['DLEXT'])
        return File.expand_path(b)
      end

    end
  end

  if add_dot_rb && (partial_name[-3..-1] != '.rb') && (partial_name[-3..-1] != '.' + RbConfig::CONFIG['DLEXT'])
    guess_discover(partial_name + '.rb') || guess_discover(partial_name + '.')
  else
    nil
  end
end

.load(filename) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/faster_require.rb', line 114

def self.load filename
  cached_marshal_data = File.open(filename, 'rb') {|f| f.read}
  begin
    @@require_locs = Marshal.restore( cached_marshal_data )
  rescue ArgumentError
    @@require_locs= {}
  end
end

.locObject



177
178
179
# File 'lib/faster_require.rb', line 177

def self.loc
  @@loc
end

.require_locsObject



169
170
171
# File 'lib/faster_require.rb', line 169

def self.require_locs
  @@require_locs
end

.sanitize(filename) ⇒ Object



20
21
22
# File 'lib/faster_require.rb', line 20

def self.sanitize filename
  filename.gsub(/[\/:]/, '_')
end

.save(to_file) ⇒ Object



189
190
191
# File 'lib/faster_require.rb', line 189

def self.save to_file
  File.open(to_file, 'wb'){|f| f.write Marshal.dump(@@require_locs)}
end

.setupObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/faster_require.rb', line 46

def self.setup
  begin
   @@dir = File.expand_path('~/.ruby_faster_require_cache')
  rescue ArgumentError => e # couldn't find HOME environment or the like
   whoami = `whoami`.strip
   if File.directory?(home = "/home/#{whoami}")
    @@dir = home + '/.ruby_faster_require_cache'
   else
     raise e.to_s + " and couldnt infer it from whoami"
   end
  end

  unless File.directory?(@@dir)
    Dir.mkdir @@dir 
    raise 'unable to create user dir for faster_require ' + @@dir unless File.directory?(@@dir)
  end
  
  config = RbConfig::CONFIG
  
  # try to be a unique, but not too long, filename, for restrictions on filename length in doze
  ruby_bin_name = config['bindir'] + config['ruby_install_name'] # needed if you have two rubies, same box, same ruby description [version, patch number]
  parts = [File.basename($0), RUBY_PATCHLEVEL.to_s, RUBY_PLATFORM, RUBY_VERSION, RUBY_VERSION, File.expand_path(File.dirname($0)), ruby_bin_name]
  unless defined?($faster_require_ignore_pwd_for_cache)
    # add in Dir.pwd
    parts << File.basename(Dir.pwd)
    parts << Dir.pwd
  else
    p 'ignoring dirpwd for cached file location' if $FAST_REQUIRE_DEBUG
  end
  
  sanitized_parts = parts.map{|part| sanitize(part)}

  full_parts_hash = string_array_cruddy_hash(parts).to_s
  
  loc_name = (sanitized_parts.map{|part| part[0..5] + (part[-5..-1] || '')}).join('-') + '-' + full_parts_hash + '.marsh'
  
  @@loc = @@dir + '/' + loc_name
  
  if File.exist?(@@loc)
    FastRequire.load @@loc
  else
    @@require_locs = {}
  end
    
  @@already_loaded = {}

  $LOADED_FEATURES.each{|already_loaded|
    # in 1.8 they might be partial paths
    # in 1.9, they might be non collapsed paths
    # so we have to sanitize them here...
    # XXXX File.exist? is a bit too loose, here...
    if File.exist?(already_loaded)
      key = File.expand_path(already_loaded)
    else
      key = FastRequire.guess_discover(already_loaded) || already_loaded
    end
    @@already_loaded[key] = true
  }

  @@already_loaded[File.expand_path(__FILE__)] = true # this file itself isn't in loaded features, yet, but very soon will be..
  # a special case--I hope...

  # also disallow re-loading $0
  @@require_locs[$0] = File.expand_path($0) # so when we run into $0 on a freak require, we will skip it...
  @@already_loaded[File.expand_path($0)] = true
  
end

.string_array_cruddy_hash(strings) ⇒ Object

appears 1.9.x has inconsistent string hashes…so roll our own…



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/faster_require.rb', line 26

def self.string_array_cruddy_hash strings
  # we only call this method once, so overflowing to a bignum is ok
  hash = 1;
  for string in strings
    hash = hash * 31
    string.each_byte{|b|
      hash += b
    }
  end
  hash # probably a Bignum (sigh)
end

Instance Method Details

#require_cached(lib) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/faster_require.rb', line 217

def require_cached lib
  lib = lib.to_s # might not be zactly 1.9 compat... to_path ??
  ALL_IN_PROCESS << [lib, @@count += 1]
  begin
    p 'doing require ' + lib + ' from ' + caller[-1] if $FAST_REQUIRE_DEBUG
    if known_loc = @@require_locs[lib]
      if @@already_loaded[known_loc]
        p 'already loaded ' + known_loc + ' ' + lib if $FAST_REQUIRE_DEBUG
        return false 
      end
      @@already_loaded[known_loc] = true
      if known_loc =~ /\.#{RbConfig::CONFIG['DLEXT']}$/
        puts 'doing original_non_cached_require on .so full path ' + known_loc if $FAST_REQUIRE_DEBUG
        original_non_cached_require known_loc # not much we can do there...too bad...well at least we pass it a full path though :P
      else
        unless $LOADED_FEATURES.include? known_loc
          if known_loc =~ /rubygems.rb$/
            puts 'requiring rubygems ' + lib if $FAST_REQUIRE_DEBUG
            original_non_cached_require(lib) # revert to normal require so rubygems doesn't freak out when it finds itself already in $LOADED_FEATURES with rubygems > 1.6 :P
          else
            IN_PROCESS << known_loc
            begin
              if $FAST_REQUIRE_DEBUG
                puts 'doing cached loc eval on ' + lib + '=>' + known_loc + " with stack:" + IN_PROCESS.join(' ')
              end
              $LOADED_FEATURES << known_loc
              # fakely add the load path, too, so that autoload for the same file/path in gems will work <sigh> [rspec2]
              no_suffix_full_path = known_loc.gsub(/\.[^.]+$/, '')
              no_suffix_lib = lib.gsub(/\.[^.]+$/, '')
              libs_path = no_suffix_full_path.gsub(no_suffix_lib, '')
              libs_path = File.expand_path(libs_path) # strip off trailing '/'
              
              $: << libs_path unless $:.index(libs_path) # add in this ones real require path, so that neighboring autoloads will work
              known_locs_dir = File.dirname(known_loc)
              $: << known_locs_dir unless $:.index(known_locs_dir) # attempt to avoid the regin loading bug...yipes.
              
              # try some more autoload conivings...so that it won't attempt to autoload if it runs into it later...
              relative_full_path = known_loc.sub(libs_path, '')[1..-1]
              $LOADED_FEATURES << relative_full_path unless $LOADED_FEATURES.index(relative_full_path) # add in with .rb, too, for autoload 
                
              # load(known_loc, false) # too slow
              
              # use eval: if this fails to load breaks re-save the offending file in binary mode, or file an issue on the faster_require tracker...
              contents = File.open(known_loc, 'rb:utf-8') {|f| f.read} # read only costs 0.34/10.0 s...
              if contents =~ /require_relative/ # =~ is faster apparently faster than .include?
                load(known_loc, false) # load is slow, but overcomes a ruby core bug: http://redmine.ruby-lang.org/issues/4487
              else
                eval(contents, TOPLEVEL_BINDING, known_loc)
              end
            ensure
              raise 'unexpected' unless IN_PROCESS.pop == known_loc
            end
            return true
          end
        else
          puts 'ignoring already loaded [circular require?] ' + known_loc + ' ' + lib if $FAST_REQUIRE_DEBUG
        end
      end
    else
      # we don't know the location--let Ruby's original require do the heavy lifting for us here
      old = $LOADED_FEATURES.dup
      p 'doing old non-known location require ' + lib if $FAST_REQUIRE_DEBUG
      if(original_non_cached_require(lib))
        # debugger might land here the first time you run a script and it doesn't have a require
        # cached yet...
        new = $LOADED_FEATURES - old
        found = new.last

        # incredibly, in 1.8.x, this doesn't always get set to a full path.
        if RUBY_VERSION < '1.9'
          if !File.file?(found)
            # discover the full path.
            dir = $:.find{|path| File.file?(path + '/' + found)}
            return true unless dir # give up, case jruby socket.jar "mysterious"
            found = dir + '/' + found
          end
          found = File.expand_path(found);
        end
        puts 'found new loc:' + lib + '=>' + found if $FAST_REQUIRE_DEBUG
        @@require_locs[lib] = found
        @@already_loaded[found] = true
        return true
      else
      
        # this is expected if it's for libraries required before faster_require was [like rbconfig]
        # raise 'actually expected' + lib if RUBY_VERSION >= '1.9.0'
        puts 'already loaded, apparently [require returned false], trying to discover how it was redundant... ' + lib if $FAST_REQUIRE_DEBUG
        # this probably was something like
        # the first pass was require 'regdeferred'
        # now it's a different require 'regdeferred.rb'
        # which fails (or vice versa)
        # so figure out why
        # calc location, expand, map back
        where_found = FastRequire.guess_discover(lib, true)
        if where_found
          puts 'inferred lib loc:' + lib + '=>' + where_found if $FAST_REQUIRE_DEBUG
          @@require_locs[lib] = where_found
          # unfortunately if it's our first pass
          # and we are in the middle of a "real" require
          # that is circular
          # then $LOADED_FEATURES or (AFAIK) nothing will have been set
          # for us to be able to assert that
          # so...I think we'll end up
          # just fudging for a bit
          #	raise 'not found' unless @@already_loaded[where_found] # should have already been set...I think...
        else
          if $FAST_REQUIRE_DEBUG
            # happens for enumerator XXXX
            puts 'unable to infer ' + lib + ' location' if $FAST_REQUIRE_DEBUG
            @@already_loaded[found] = true # so hacky...
          end
        end
        return false # XXXX test all these return values
      end
    end
  ensure
    raise 'huh' unless ALL_IN_PROCESS.pop[0] == lib
  end
end