dependencies.rb 17.8 KB
Newer Older
1
require 'set'
2
require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
3
require File.dirname(__FILE__) + '/core_ext/load_error'
4
require File.dirname(__FILE__) + '/core_ext/kernel'
5

6
module Dependencies #:nodoc:
7
  extend self
8

9 10 11 12
  # Should we turn on Ruby warnings on the first load of dependent files?
  mattr_accessor :warnings_on_first_load
  self.warnings_on_first_load = false

13 14 15 16 17
  # All files ever loaded.
  mattr_accessor :history
  self.history = Set.new

  # All files currently loaded.
18
  mattr_accessor :loaded
19
  self.loaded = Set.new
20

21
  # Should we load files or require them?
22
  mattr_accessor :mechanism
23
  self.mechanism = :load
24 25 26 27 28 29 30 31 32 33 34 35
  
  # The set of directories from which we may automatically load files. Files
  # under these directories will be reloaded on each request in development mode,
  # unless the directory also appears in load_once_paths.
  mattr_accessor :load_paths
  self.load_paths = []
  
  # The set of directories from which automatically loaded constants are loaded
  # only once. All directories in this set must also be present in +load_paths+.
  mattr_accessor :load_once_paths
  self.load_once_paths = []
  
36 37
  # An array of qualified constant names that have been loaded. Adding a name to
  # this array will cause it to be unloaded the next time Dependencies are cleared.
38 39 40
  mattr_accessor :autoloaded_constants
  self.autoloaded_constants = []
  
41 42 43 44 45
  # An array of constant names that need to be unloaded on every request. Used
  # to allow arbitrary constants to be marked for unloading.
  mattr_accessor :explicitly_unloadable_constants
  self.explicitly_unloadable_constants = []
  
46 47 48 49
  # Set to true to enable logging of const_missing and file loads
  mattr_accessor :log_activity
  self.log_activity = false
  
50 51 52 53 54
  # :nodoc:
  # An internal stack used to record which constants are loaded by any block.
  mattr_accessor :constant_watch_stack
  self.constant_watch_stack = []
  
55 56 57
  def load?
    mechanism == :load
  end
58

59
  def depend_on(file_name, swallow_load_errors = false)
60
    path = search_for_file(file_name)
61
    require_or_load(path || file_name)
62 63
  rescue LoadError
    raise unless swallow_load_errors
64 65 66 67 68
  end

  def associate_with(file_name)
    depend_on(file_name, true)
  end
69

70
  def clear
71
    log_call
72
    loaded.clear
73
    remove_unloadable_constants!
74
  end
75

76
  def require_or_load(file_name, const_path = nil)
77
    log_call file_name, const_path
78
    file_name = $1 if file_name =~ /^(.*)\.rb$/
79 80
    expanded = File.expand_path(file_name)
    return if loaded.include?(expanded)
81

82 83
    # Record that we've seen this file *before* loading it to avoid an
    # infinite loop with mutual dependencies.
84
    loaded << expanded
85
    
86
    if load?
87
      log "loading #{file_name}"
88
      begin
89 90
        # Enable warnings iff this file has not been loaded before and
        # warnings_on_first_load is set.
91 92 93
        load_args = ["#{file_name}.rb"]
        load_args << const_path unless const_path.nil?
        
94
        if !warnings_on_first_load or history.include?(expanded)
95
          result = load_file(*load_args)
96
        else
97
          enable_warnings { result = load_file(*load_args) }
98
        end
N
Nicholas Seckar 已提交
99
      rescue Exception
100
        loaded.delete expanded
101
        raise
102
      end
103
    else
104
      log "requiring #{file_name}"
105
      result = require file_name
106
    end
107

108
    # Record history *after* loading so first load gets warnings.
109
    history << expanded
110
    return result
111 112
  end
  
113 114 115 116
  # Is the provided constant path defined?
  def qualified_const_defined?(path)
    raise NameError, "#{path.inspect} is not a valid constant name!" unless
      /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path
117 118 119 120 121 122 123 124 125 126 127
    
    names = path.split('::')
    names.shift if names.first.empty?
    
    # We can't use defined? because it will invoke const_missing for the parent
    # of the name we are checking.
    names.inject(Object) do |mod, name|
      return false unless mod.const_defined? name
      mod.const_get name
    end
    return true
128 129
  end
  
130 131 132 133
  # Given +path+, a filesystem path to a ruby file, return an array of constant
  # paths which would cause Dependencies to attempt to load this file.
  # 
  def loadable_constants_for_path(path, bases = load_paths - load_once_paths)
134 135
    path = $1 if path =~ /\A(.*)\.rb\Z/
    expanded_path = File.expand_path(path)
136
    
137
    bases.collect do |root|
138 139
      expanded_root = File.expand_path(root)
      next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
140 141 142 143 144
      
      nesting = expanded_path[(expanded_root.size)..-1]
      nesting = nesting[1..-1] if nesting && nesting[0] == ?/
      next if nesting.blank?
      
145 146 147 148 149
      [
        nesting.camelize,
        # Special case: application.rb might define ApplicationControlller.
        ('ApplicationController' if nesting == 'application')
      ]
150
    end.flatten.compact.uniq
151 152
  end
  
153 154
  # Search for a file in load_paths matching the provided suffix.
  def search_for_file(path_suffix)
155
    path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb'
156
    load_paths.each do |root|
157 158 159 160 161 162 163
      path = File.join(root, path_suffix)
      return path if File.file? path
    end
    nil # Gee, I sure wish we had first_match ;-)
  end
  
  # Does the provided path_suffix correspond to an autoloadable module?
164
  # Instead of returning a boolean, the autoload base for this module is returned. 
165
  def autoloadable_module?(path_suffix)
166 167
    load_paths.each do |load_path|
      return load_path if File.directory? File.join(load_path, path_suffix)
168
    end
169 170 171 172 173 174 175 176 177 178 179 180 181 182
    nil
  end
  
  # Attempt to autoload the provided module name by searching for a directory
  # matching the expect path suffix. If found, the module is created and assigned
  # to +into+'s constants with the name +const_name+. Provided that the directory
  # was loaded from a reloadable base path, it is added to the set of constants
  # that are to be unloaded.
  def autoload_module!(into, const_name, qualified_name, path_suffix)
    return nil unless base_path = autoloadable_module?(path_suffix)
    mod = Module.new
    into.const_set const_name, mod
    autoloaded_constants << qualified_name unless load_once_paths.include?(base_path)
    return mod
183 184 185 186 187 188 189 190 191
  end
  
  # Load the file at the provided path. +const_paths+ is a set of qualified
  # constant names. When loading the file, Dependencies will watch for the
  # addition of these constants. Each that is defined will be marked as
  # autoloaded, and will be removed when Dependencies.clear is next called.
  # 
  # If the second parameter is left off, then Dependencies will construct a set
  # of names that the file at +path+ may define. See
192 193
  # +loadable_constants_for_path+ for more details.
  def load_file(path, const_paths = loadable_constants_for_path(path))
194
    log_call path, const_paths
195
    const_paths = [const_paths].compact unless const_paths.is_a? Array
196
    parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }
197
    
198 199 200 201
    result = nil
    newly_defined_paths = new_constants_in(*parent_paths) do
      result = load_without_new_constant_marking path
    end
202
    
203
    autoloaded_constants.concat newly_defined_paths
204
    autoloaded_constants.uniq!
205
    log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
206
    return result
207 208 209 210 211 212 213 214 215 216 217 218
  end
  
  # Return the constant path for the provided parent and constant name.
  def qualified_name_for(mod, name)
    mod_name = to_constant_name mod
    (%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}"
  end
  
  # Load the constant named +const_name+ which is missing from +from_mod+. If
  # it is not possible to laod the constant into from_mod, try its parent module
  # using const_missing.
  def load_missing_constant(from_mod, const_name)
219
    log_call from_mod, const_name
220 221 222 223 224 225 226 227 228
    if from_mod == Kernel
      if ::Object.const_defined?(const_name)
        log "Returning Object::#{const_name} for Kernel::#{const_name}"
        return ::Object.const_get(const_name)
      else
        log "Substituting Object for Kernel"
        from_mod = Object
      end
    end
229
    
230 231 232
    # If we have an anonymous module, all we can do is attempt to load from Object.
    from_mod = Object if from_mod.name.empty?
    
233 234 235 236
    unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id
      raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
    end
    
237 238
    raise ArgumentError, "Expected #{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name)
    
239 240 241 242
    qualified_name = qualified_name_for from_mod, const_name
    path_suffix = qualified_name.underscore
    name_error = NameError.new("uninitialized constant #{qualified_name}")
    
243
    file_path = search_for_file(path_suffix)
244
    if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
245
      require_or_load file_path
246 247
      raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name)
      return from_mod.const_get(const_name)
248
    elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
      return mod
    elsif (parent = from_mod.parent) && parent != from_mod &&
          ! from_mod.parents.any? { |p| p.const_defined?(const_name) }
      # If our parents do not have a constant named +const_name+ then we are free
      # to attempt to load upwards. If they do have such a constant, then this
      # const_missing must be due to from_mod::const_name, which should not
      # return constants from from_mod's parents.
      begin
        return parent.const_missing(const_name)
      rescue NameError => e
        raise unless e.missing_name? qualified_name_for(parent, const_name)
        raise name_error
      end
    else
      raise name_error
    end
  end
  
267 268 269 270 271 272
  # Remove the constants that have been autoloaded, and those that have been
  # marked for unloading.
  def remove_unloadable_constants!
    autoloaded_constants.each { |const| remove_constant const }
    autoloaded_constants.clear
    explicitly_unloadable_constants.each { |const| remove_constant const }
273 274 275 276 277 278 279
  end
  
  # Determine if the given constant has been automatically loaded.
  def autoloaded?(desc)
    name = to_constant_name desc
    return false unless qualified_const_defined? name
    return autoloaded_constants.include?(name)
280 281
  end
  
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
  # Will the provided constant descriptor be unloaded?
  def will_unload?(const_desc)
    autoloaded?(desc) ||
      explicitly_unloadable_constants.include?(to_constant_name(const_desc))
  end
  
  # Mark the provided constant name for unloading. This constant will be
  # unloaded on each request, not just the next one.
  def mark_for_unload(const_desc)
    name = to_constant_name const_desc
    if explicitly_unloadable_constants.include? name
      return false
    else
      explicitly_unloadable_constants << name
      return true
    end
  end
  
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 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
  # Run the provided block and detect the new constants that were loaded during
  # its execution. Constants may only be regarded as 'new' once -- so if the
  # block calls +new_constants_in+ again, then the constants defined within the
  # inner call will not be reported in this one.
  def new_constants_in(*descs)
    log_call(*descs)
    
    # Build the watch frames. Each frame is a tuple of
    #   [module_name_as_string, constants_defined_elsewhere]
    watch_frames = descs.collect do |desc|
      if desc.is_a? Module
        mod_name = desc.name
        initial_constants = desc.constants
      elsif desc.is_a?(String) || desc.is_a?(Symbol)
        mod_name = desc.to_s
        
        # Handle the case where the module has yet to be defined.
        initial_constants = if qualified_const_defined?(mod_name)
          mod_name.constantize.constants
        else
         []
        end
      else
        raise Argument, "#{desc.inspect} does not describe a module!"
      end
      
      [mod_name, initial_constants]
    end
    
    constant_watch_stack.concat watch_frames
    
    yield # Now yield to the code that is to define new constants.
    
    # Find the new constants.
    new_constants = watch_frames.collect do |mod_name, prior_constants|
      # Module still doesn't exist? Treat it as if it has no constants.
      next [] unless qualified_const_defined?(mod_name)
      
      mod = mod_name.constantize
      next [] unless mod.is_a? Module
      new_constants = mod.constants - prior_constants
      
      # Make sure no other frames takes credit for these constants.
      constant_watch_stack.each do |frame_name, constants|
        constants.concat new_constants if frame_name == mod_name
      end
      
      new_constants.collect do |suffix|
        mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}"
      end
    end.flatten
    
    log "New constants: #{new_constants * ', '}"
    return new_constants
  ensure
    # Remove the stack frames that we added.
    if defined?(watch_frames) && ! watch_frames.empty?
      frame_ids = watch_frames.collect(&:object_id)
      constant_watch_stack.delete_if do |watch_frame|
        frame_ids.include? watch_frame.object_id
      end
    end
  end
  
364 365 366 367 368 369 370 371 372
  class LoadingModule
    # Old style environment.rb referenced this method directly.  Please note, it doesn't
    # actualy *do* anything any more.
    def self.root(*args)
      if defined?(RAILS_DEFAULT_LOGGER)
        RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
        RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
      end
    end
373
  end
374 375 376 377 378 379 380 381 382

protected
  
  # Convert the provided const desc to a qualified constant name (as a string).
  # A module, class, symbol, or string may be provided.
  def to_constant_name(desc)
    name = case desc
      when String then desc.starts_with?('::') ? desc[2..-1] : desc
      when Symbol then desc.to_s
383 384 385
      when Module
        raise ArgumentError, "Anonymous modules have no name to be referenced by" if desc.name.blank?
        desc.name
386 387 388 389
      else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
    end
  end
  
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
  def remove_constant(const)
    return false unless qualified_const_defined? const
    
    names = const.split('::')
    if names.size == 1 || names.first.empty? # It's under Object
      parent = Object
    else
      parent = (names[0..-2] * '::').constantize
    end
    
    log "removing constant #{const}"
    parent.send :remove_const, names.last
    return true
  end
  
405 406 407 408 409 410 411 412 413 414 415 416 417
  def log_call(*args)
    arg_str = args.collect(&:inspect) * ', '
    /in `([a-z_\?\!]+)'/ =~ caller(1).first
    selector = $1 || '<unknown>' 
    log "called #{selector}(#{arg_str})"
  end
  
  def log(msg)
    if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity
      RAILS_DEFAULT_LOGGER.debug "Dependencies: #{msg}"
    end
  end
  
418 419
end

420
Object.send(:define_method, :require_or_load)     { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
421 422
Object.send(:define_method, :require_dependency)  { |file_name| Dependencies.depend_on(file_name) }       unless Object.respond_to?(:require_dependency)
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) }  unless Object.respond_to?(:require_association)
423

424
class Module #:nodoc:
425 426
  # Rename the original handler so we can chain it to the new one
  alias :rails_original_const_missing :const_missing
427
  
428 429 430
  # Use const_missing to autoload associations so we don't have to
  # require_association when using single-table inheritance.
  def const_missing(class_id)
431
    Dependencies.load_missing_constant self, class_id
432
  end
433 434 435 436 437
  
  def unloadable(const_desc = self)
    super(const_desc)
  end
  
438
end
439

440 441 442 443 444
class Class
  def const_missing(class_id)
    if [Object, Kernel].include?(self) || parent == self
      super
    else
445
      begin
446 447 448 449 450
        begin
          Dependencies.load_missing_constant self, class_id
        rescue NameError
          parent.send :const_missing, class_id
        end
451 452
      rescue NameError => e
        # Make sure that the name we are missing is the one that caused the error
453
        parent_qualified_name = Dependencies.qualified_name_for parent, class_id
454
        raise unless e.missing_name? parent_qualified_name
455
        qualified_name = Dependencies.qualified_name_for self, class_id
456 457
        raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
      end
458 459 460 461
    end
  end
end

462
class Object #:nodoc:
463 464 465
  
  alias_method :load_without_new_constant_marking, :load
  
466
  def load(file, *extras)
467
    Dependencies.new_constants_in(Object) { super(file, *extras) }
468
  rescue Exception => exception  # errors from loading file
469 470
    exception.blame_file! file
    raise
471
  end
472

473
  def require(file, *extras)
474
    Dependencies.new_constants_in(Object) { super(file, *extras) }
475
  rescue Exception => exception  # errors from required file
476 477
    exception.blame_file! file
    raise
478
  end
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496

  # Mark the given constant as unloadable. Unloadable constants are removed each
  # time dependencies are cleared.
  # 
  # Note that marking a constant for unloading need only be done once. Setup
  # or init scripts may list each unloadable constant that may need unloading;
  # each constant will be removed for every subsequent clear, as opposed to for
  # the first clear.
  # 
  # The provided constant descriptor may be a (non-anonymous) module or class,
  # or a qualified constant name as a string or symbol.
  # 
  # Returns true if the constant was not previously marked for unloading, false
  # otherwise.
  def unloadable(const_desc)
    Dependencies.mark_for_unload const_desc
  end

497 498 499
end

# Add file-blaming to exceptions
500
class Exception #:nodoc:
501 502 503
  def blame_file!(file)
    (@blamed_files ||= []).unshift file
  end
504

505 506 507
  def blamed_files
    @blamed_files ||= []
  end
508

509 510
  def describe_blame
    return nil if blamed_files.empty?
511
    "This error occurred while loading the following files:\n   #{blamed_files.join "\n   "}"
512
  end
513 514 515 516 517

  def copy_blame!(exc)
    @blamed_files = exc.blamed_files.clone
    self
  end
518
end