dependencies.rb 21.7 KB
Newer Older
J
Jeremy Kemper 已提交
1
require 'set'
J
Jeremy Kemper 已提交
2
require 'thread'
3
require 'pathname'
4 5 6
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/introspection'
Y
Yehuda Katz 已提交
7
require 'active_support/core_ext/module/anonymous'
8 9 10 11 12
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
require 'active_support/inflector'
J
Jeremy Kemper 已提交
13

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
module ActiveSupport #:nodoc:
  module Dependencies #:nodoc:
    extend self

    # 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

    # All files ever loaded.
    mattr_accessor :history
    self.history = Set.new

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

    # Should we load files or require them?
    mattr_accessor :mechanism
32
    self.mechanism = ENV['NO_RELOAD'] ? :require : :load
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,
36 37 38
    # unless the directory also appears in autoload_once_paths.
    mattr_accessor :autoload_paths
    self.autoload_paths = []
39 40

    # The set of directories from which automatically loaded constants are loaded
41 42 43
    # only once. All directories in this set must also be present in +autoload_paths+.
    mattr_accessor :autoload_once_paths
    self.autoload_once_paths = []
44 45 46 47 48 49

    # 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.
    mattr_accessor :autoloaded_constants
    self.autoloaded_constants = []

W
wycats 已提交
50 51 52
    mattr_accessor :references
    self.references = {}

53 54 55 56 57
    # 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 = []

58 59 60 61
    # The logger is used for generating information on the action run-time (including benchmarking) if available.
    # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
    mattr_accessor :logger

62 63 64 65
    # Set to true to enable logging of const_missing and file loads
    mattr_accessor :log_activity
    self.log_activity = false

66 67 68 69 70 71
    class WatchStack < Array
      def initialize
        @mutex = Mutex.new
      end

      def self.locked(*methods)
72
        methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ }
73 74 75 76 77
      end

      locked :concat, :each, :delete_if, :<<

      def new_constants_for(frames)
78 79
        constants = []
        frames.each do |mod_name, prior_constants|
80
          mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name)
81 82 83
          next unless mod.is_a?(Module)

          new_constants = mod.local_constant_names - prior_constants
84 85 86 87 88 89 90 91 92 93 94 95

          # If we are checking for constants under, say, :Object, nested under something
          # else that is checking for constants also under :Object, make sure the
          # parent knows that we have found, and taken care of, the constant.
          #
          # In particular, this means that since Kernel.require discards the constants
          # it finds, parents will be notified that about those constants, and not
          # consider them "new". As a result, they will not be added to the
          # autoloaded_constants list.
          each do |key, value|
            value.concat(new_constants) if key == mod_name
          end
96

97 98
          new_constants.each do |suffix|
            constants << ([mod_name, suffix] - ["Object"]).join("::")
99
          end
100 101
        end
        constants
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
      end

      # Add a set of modules to the watch stack, remembering the initial constants
      def add_modules(modules)
        list = modules.map do |desc|
          name = Dependencies.to_constant_name(desc)
          consts = Dependencies.qualified_const_defined?(name) ?
            Inflector.constantize(name).local_constant_names : []
          [name, consts]
        end
        concat(list)
        list
      end

      def lock
        @mutex.synchronize { yield self }
      end
    end

121 122
    # An internal stack used to record which constants are loaded by any block.
    mattr_accessor :constant_watch_stack
123
    self.constant_watch_stack = WatchStack.new
124

125 126
    # Module includes this module
    module ModuleConstMissing #:nodoc:
127
      def self.append_features(base)
128
        base.class_eval do
129
          # Emulate #exclude via an ivar
130
          return if defined?(@_const_missing) && @_const_missing
131
          @_const_missing = instance_method(:const_missing)
132
          remove_method(:const_missing)
133
        end
134
        super
135
      end
136

137
      def self.exclude_from(base)
138
        base.class_eval do
139 140
          define_method :const_missing, @_const_missing
          @_const_missing = nil
141 142 143
        end
      end

144 145
      # Use const_missing to autoload associations so we don't have to
      # require_association when using single-table inheritance.
146 147 148 149 150 151 152 153 154 155
      def const_missing(const_name, nesting = nil)
        klass_name = name.presence || "Object"

        if !nesting
          # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"]
          # even though it might not be, such as in the case of
          # class Foo::Bar; Baz; end
          nesting = []
          klass_name.to_s.scan(/::|$/) { nesting.unshift $` }
        end
156

157 158 159
        # If there are multiple levels of nesting to search under, the top
        # level is the one we want to report as the lookup fail.
        error = nil
160

161
        nesting.each do |namespace|
162
          begin
J
Jeremy Kemper 已提交
163
            return Dependencies.load_missing_constant Inflector.constantize(namespace), const_name
164
          rescue NoMethodError then raise
165
          rescue NameError => e
166
            error ||= e
167 168
          end
        end
169 170 171 172 173 174 175 176 177

        # Raise the first error for this set. If this const_missing came from an
        # earlier const_missing, this will result in the real error bubbling
        # all the way up
        raise error
      end

      def unloadable(const_desc = self)
        super(const_desc)
178 179 180 181 182
      end
    end

    # Object includes this module
    module Loadable #:nodoc:
183 184
      def self.exclude_from(base)
        base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
185 186 187 188 189 190
      end

      def require_or_load(file_name)
        Dependencies.require_or_load(file_name)
      end

191
      def require_dependency(file_name, message = "No such file to load -- %s")
Y
Right.  
Yehuda Katz 已提交
192 193 194 195
        unless file_name.is_a?(String)
          raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
        end

196
        Dependencies.depend_on(file_name, false, message)
197 198 199 200 201 202
      end

      def require_association(file_name)
        Dependencies.associate_with(file_name)
      end

203
      def load_dependency(file)
204
        if Dependencies.load?
205
          Dependencies.new_constants_in(Object) { yield }.presence
206
        else
207
          yield
208
        end
209 210 211 212 213
      rescue Exception => exception  # errors from loading file
        exception.blame_file! file
        raise
      end

214 215 216 217 218 219
      def load(file, *)
        load_dependency(file) { super }
      end

      def require(file, *)
        load_dependency(file) { super }
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
      end

      # 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
    end

    # Exception file-blaming
    module Blamable #:nodoc:
      def blame_file!(file)
        (@blamed_files ||= []).unshift file
      end

      def blamed_files
        @blamed_files ||= []
      end

      def describe_blame
        return nil if blamed_files.empty?
        "This error occurred while loading the following files:\n   #{blamed_files.join "\n   "}"
      end

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

261
    def hook!
262 263 264
      Object.class_eval { include Loadable }
      Module.class_eval { include ModuleConstMissing }
      Exception.class_eval { include Blamable }
265 266 267 268
      true
    end

    def unhook!
269 270
      ModuleConstMissing.exclude_from(Module)
      Loadable.exclude_from(Object)
271
      true
272 273
    end

274 275 276
    def load?
      mechanism == :load
    end
277

278
    def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
279 280
      path = search_for_file(file_name)
      require_or_load(path || file_name)
281 282 283
    rescue LoadError => load_error
      unless swallow_load_errors
        if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
284
          raise LoadError.new(message % file_name).copy_blame!(load_error)
285 286 287
        end
        raise
      end
288
    end
289

290 291 292
    def associate_with(file_name)
      depend_on(file_name, true)
    end
293

294 295 296 297 298
    def clear
      log_call
      loaded.clear
      remove_unloadable_constants!
    end
299

300 301 302 303 304
    def require_or_load(file_name, const_path = nil)
      log_call file_name, const_path
      file_name = $1 if file_name =~ /^(.*)\.rb$/
      expanded = File.expand_path(file_name)
      return if loaded.include?(expanded)
305

306 307 308
      # Record that we've seen this file *before* loading it to avoid an
      # infinite loop with mutual dependencies.
      loaded << expanded
309

310 311 312 313 314 315 316 317 318 319 320 321 322 323
      begin
        if load?
          log "loading #{file_name}"

          # Enable warnings iff this file has not been loaded before and
          # warnings_on_first_load is set.
          load_args = ["#{file_name}.rb"]
          load_args << const_path unless const_path.nil?

          if !warnings_on_first_load or history.include?(expanded)
            result = load_file(*load_args)
          else
            enable_warnings { result = load_file(*load_args) }
          end
324
        else
325 326
          log "requiring #{file_name}"
          result = require file_name
327
        end
328 329 330
      rescue Exception
        loaded.delete expanded
        raise
331 332
      end

333 334 335 336
      # Record history *after* loading so first load gets warnings.
      history << expanded
      return result
    end
337

338 339
    # Is the provided constant path defined?
    def qualified_const_defined?(path)
340
      names = path.sub(/^::/, '').to_s.split('::')
341

342
      names.inject(Object) do |mod, name|
343
        return false unless local_const_defined?(mod, name)
344 345
        mod.const_get name
      end
346
    end
347

348 349
    if Module.method(:const_defined?).arity == 1
      # Does this module define this constant?
350
      # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9
351
      def local_const_defined?(mod, const)
352 353 354
        mod.const_defined?(const)
      end
    else
355
      def local_const_defined?(mod, const) #:nodoc:
356 357
        mod.const_defined?(const, false)
      end
358
    end
359

360 361
    # 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.
362
    def loadable_constants_for_path(path, bases = autoload_paths)
363 364 365 366 367 368 369 370 371 372 373
      path = $1 if path =~ /\A(.*)\.rb\Z/
      expanded_path = File.expand_path(path)
      paths = []

      bases.each do |root|
        expanded_root = File.expand_path(root)
        next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path

        nesting = expanded_path[(expanded_root.size)..-1]
        nesting = nesting[1..-1] if nesting && nesting[0] == ?/
        next if nesting.blank?
374 375

        paths << nesting.camelize
376 377 378 379
      end

      paths.uniq!
      paths
380
    end
381

382
    # Search for a file in autoload_paths matching the provided suffix.
383
    def search_for_file(path_suffix)
384 385
      path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")

386
      autoload_paths.each do |root|
387 388 389 390
        path = File.join(root, path_suffix)
        return path if File.file? path
      end
      nil # Gee, I sure wish we had first_match ;-)
391
    end
392

393 394 395
    # Does the provided path_suffix correspond to an autoloadable module?
    # Instead of returning a boolean, the autoload base for this module is returned.
    def autoloadable_module?(path_suffix)
396
      autoload_paths.each do |load_path|
397 398 399
        return load_path if File.directory? File.join(load_path, path_suffix)
      end
      nil
400
    end
401

402
    def load_once_path?(path)
403
      autoload_once_paths.any? { |base| path.starts_with? base }
404
    end
405

406 407 408 409 410 411 412 413 414
    # 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
415
      autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
416 417
      return mod
    end
418

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
    # 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
    # +loadable_constants_for_path+ for more details.
    def load_file(path, const_paths = loadable_constants_for_path(path))
      log_call path, const_paths
      const_paths = [const_paths].compact unless const_paths.is_a? Array
      parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }

      result = nil
      newly_defined_paths = new_constants_in(*parent_paths) do
434
        result = Kernel.load path
435
      end
J
Jeremy Kemper 已提交
436

437 438 439 440 441
      autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
      autoloaded_constants.uniq!
      log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
      return result
    end
J
Jeremy Kemper 已提交
442

443 444 445
    # Return the constant path for the provided parent and constant name.
    def qualified_name_for(mod, name)
      mod_name = to_constant_name mod
446
      mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}"
447
    end
448

449 450 451 452 453
    # Load the constant named +const_name+ which is missing from +from_mod+. If
    # it is not possible to load the constant into from_mod, try its parent module
    # using const_missing.
    def load_missing_constant(from_mod, const_name)
      log_call from_mod, const_name
454

455
      unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
456 457 458
        raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
      end

459
      raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if local_const_defined?(from_mod, const_name)
460 461 462

      qualified_name = qualified_name_for from_mod, const_name
      path_suffix = qualified_name.underscore
463

464
      trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}}
465
      name_error = NameError.new("uninitialized constant #{qualified_name}")
466
      name_error.set_backtrace(trace)
467 468

      file_path = search_for_file(path_suffix)
469

470 471
      if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
        require_or_load file_path
472
        raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name)
473 474 475 476
        return from_mod.const_get(const_name)
      elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
        return mod
      elsif (parent = from_mod.parent) && parent != from_mod &&
477
            ! from_mod.parents.any? { |p| local_const_defined?(p, const_name) }
478 479 480 481 482 483 484 485 486 487 488
        # 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
489 490 491
        raise name_error
      end
    end
492

493 494 495 496 497
    # 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
498
      Reference.clear!
499 500
      explicitly_unloadable_constants.each { |const| remove_constant const }
    end
501

W
wycats 已提交
502
    class Reference
503 504
      @@constants = Hash.new { |h, k| h[k] = Inflector.constantize(k) }

505 506
      attr_reader :name

507 508 509
      def initialize(name)
        @name = name.to_s
        @@constants[@name] = name if name.respond_to?(:name)
W
wycats 已提交
510 511 512
      end

      def get
513
        @@constants[@name]
W
wycats 已提交
514 515
      end

516 517
      def self.clear!
        @@constants.clear
W
wycats 已提交
518 519 520 521
      end
    end

    def ref(name)
522 523 524 525 526
      references[name] ||= Reference.new(name)
    end

    def constantize(name)
      ref(name).get
W
wycats 已提交
527 528
    end

529 530 531
    # Determine if the given constant has been automatically loaded.
    def autoloaded?(desc)
      # No name => anonymous module.
532
      return false if desc.is_a?(Module) && desc.anonymous?
533 534 535 536
      name = to_constant_name desc
      return false unless qualified_const_defined? name
      return autoloaded_constants.include?(name)
    end
537

538 539
    # Will the provided constant descriptor be unloaded?
    def will_unload?(const_desc)
540
      autoloaded?(const_desc) ||
541 542
        explicitly_unloadable_constants.include?(to_constant_name(const_desc))
    end
543

544 545 546 547 548 549 550 551 552 553
    # 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
554
    end
555

556 557 558 559 560 561 562 563 564 565
    # 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.
    #
    # If the provided block does not run to completion, and instead raises an
    # exception, any new constants are regarded as being only partially defined
    # and will be removed immediately.
    def new_constants_in(*descs)
      log_call(*descs)
566
      watch_frames = constant_watch_stack.add_modules(descs)
567

568 569 570 571 572
      aborting = true
      begin
        yield # Now yield to the code that is to define new constants.
        aborting = false
      ensure
573
        new_constants = constant_watch_stack.new_constants_for(watch_frames)
574 575

        log "New constants: #{new_constants * ', '}"
576
        return new_constants unless aborting
577

578 579
        log "Error during loading, removing partially loaded constants "
        new_constants.each {|c| remove_constant(c) }.clear
580
      end
581

582
      return []
583 584
    ensure
      # Remove the stack frames that we added.
585
      watch_frames.each {|f| constant_watch_stack.delete(f) } if watch_frames.present?
586
    end
587

588 589 590 591
    class LoadingModule #:nodoc:
      # Old style environment.rb referenced this method directly.  Please note, it doesn't
      # actually *do* anything any more.
      def self.root(*args)
592
        if defined?(Rails) && Rails.logger
593 594
          Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
          Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
595
        end
596 597
      end
    end
598

599 600 601
    # 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) #:nodoc:
S
Santiago Pastorino 已提交
602
      case desc
603
        when String then desc.sub(/^::/, '')
604 605
        when Symbol then desc.to_s
        when Module
606 607
          desc.name.presence ||
            raise(ArgumentError, "Anonymous modules have no name to be referenced by")
608
        else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
609 610
      end
    end
611

612 613
    def remove_constant(const) #:nodoc:
      return false unless qualified_const_defined? const
J
Jeremy Kemper 已提交
614

615 616 617 618
      # Normalize ::Foo, Foo, Object::Foo, and ::Object::Foo to Object::Foo
      names = const.to_s.sub(/^::(Object)?/, 'Object::').split("::")
      to_remove = names.pop
      parent = Inflector.constantize(names * '::')
J
Jeremy Kemper 已提交
619

620
      log "removing constant #{const}"
621
      parent.instance_eval { remove_const to_remove }
W
wycats 已提交
622

623
      return true
624
    end
J
Jeremy Kemper 已提交
625

626 627
    protected
      def log_call(*args)
628
        if logger && log_activity
629
          arg_str = args.collect { |arg| arg.inspect } * ', '
630 631 632 633 634
          /in `([a-z_\?\!]+)'/ =~ caller(1).first
          selector = $1 || '<unknown>'
          log "called #{selector}(#{arg_str})"
        end
      end
635

636
      def log(msg)
637 638
        if logger && log_activity
          logger.debug "Dependencies: #{msg}"
639 640
        end
      end
641
  end
642 643
end

644
ActiveSupport::Dependencies.hook!