diff options
author | Hiroshi SHIBATA <[email protected]> | 2020-12-08 16:33:39 +0900 |
---|---|---|
committer | Hiroshi SHIBATA <[email protected]> | 2020-12-08 17:30:02 +0900 |
commit | 4aca77edde91f826aa243e268bf1ef5214530583 () | |
tree | ef0cf1a95fcced00ca5fa40f3412c567bf95d705 /lib/rubygems/resolver | |
parent | 6a6a24df9b72750d12f9b15192bdb7517e668efb (diff) |
Merge prepare version of RubyGems 3.2.0
Notes: Merged: https://.com/ruby/ruby/pull/3864
28 files changed, 746 insertions, 222 deletions
@@ -28,12 +28,20 @@ class Gem::Resolver::ActivationRequest when Gem::Specification @spec == other when Gem::Resolver::ActivationRequest - @spec == other.spec && @request == other.request else false end end ## # Is this activation request for a development dependency? @@ -46,6 +46,10 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification @dependencies == other.dependencies end def fetch_development_dependencies # :nodoc: spec = source.fetch_spec Gem::NameTuple.new @name, @version, @platform @@ -53,7 +57,7 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification end def installable_platform? # :nodoc: - Gem::Platform.match @platform end def pretty_print(q) # :nodoc: @@ -85,7 +85,7 @@ class Gem::Resolver::Conflict activated, requirement, request_path(@activated).reverse.join(", depends on\n "), request_path(@failed_dep).reverse.join(", depends on\n "), - matching, ] end @@ -28,7 +28,7 @@ class Gem::Resolver::DependencyRequest when Gem::Dependency @dependency == other when Gem::Resolver::DependencyRequest - @dependency == other.dependency && @requester == other.requester else false end @@ -33,6 +33,17 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification spec.dependencies end def inspect # :nodoc: '#<%s %s source %s>' % [self.class, full_name, @source] end @@ -32,7 +32,6 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set super() @domain = domain - @remote = consider_remote? @f = Gem::SpecFetcher.fetcher @@ -170,7 +169,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set always_install = @always_install.map {|s| s.full_name } '#<%s domain: %s specs: %p always install: %p>' % [ - self.class, @domain, @specs.keys, always_install, ] end @@ -28,7 +28,7 @@ class Gem::Resolver::LockSet < Gem::Resolver::Set def add(name, version, platform) # :nodoc: version = Gem::Version.new version specs = [ - Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform) ] @specs.concat specs @@ -1,9 +1,10 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/gem_metadata' -require 'rubygems/resolver/molinillo/lib/molinillo/errors' -require 'rubygems/resolver/molinillo/lib/molinillo/resolver' -require 'rubygems/resolver/molinillo/lib/molinillo/modules/ui' -require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' # Gem::Resolver::Molinillo is a generic dependency resolution algorithm. module Gem::Resolver::Molinillo @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # @!visibility private module Delegates @@ -45,6 +46,12 @@ module Gem::Resolver::Molinillo current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty current_state.conflicts end end end end @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo module Delegates # Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a @@ -1,9 +1,10 @@ # frozen_string_literal: true require 'set' require 'tsort' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex' module Gem::Resolver::Molinillo # A directed acyclic graph that is tuned to hold named dependencies @@ -123,6 +124,7 @@ module Gem::Resolver::Molinillo dot.join("\n") end # @return [Boolean] whether the two dependency graphs are equal, determined # by a recursive traversal of each {#root_vertices} and its # {Vertex#successors} @@ -147,8 +149,8 @@ module Gem::Resolver::Molinillo vertex = add_vertex(name, payload, root) vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| - parent_node = vertex_named(parent_name) - add_edge(parent_node, vertex, requirement) end vertex end @@ -189,7 +191,7 @@ module Gem::Resolver::Molinillo # @return [Edge] the added edge def add_edge(origin, destination, requirement) if destination.path_to?(origin) - raise CircularDependencyError.new([origin, destination]) end add_edge_no_circular(origin, destination, requirement) end @@ -218,5 +220,37 @@ module Gem::Resolver::Molinillo def add_edge_no_circular(origin, destination, requirement) log.add_edge_no_circular(self, origin.name, destination.name, requirement) end end end @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo class DependencyGraph # An action that modifies a {DependencyGraph} that is reversible. @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -1,10 +1,11 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag' module Gem::Resolver::Molinillo class DependencyGraph @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -13,11 +14,11 @@ module Gem::Resolver::Molinillo end # (see Action#up) - def up(_graph) end # (see Action#down) - def down(_graph) end # @!group Tag @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo class DependencyGraph # A vertex in a {DependencyGraph} that encapsulates a {#name} and a @@ -32,7 +33,7 @@ module Gem::Resolver::Molinillo # @return [Array<Object>] all of the requirements that required # this vertex def requirements - incoming_edges.map(&:requirement) + explicit_requirements end # @return [Array<Edge>] the edges of {#graph} that have `self` as their @@ -49,14 +50,25 @@ module Gem::Resolver::Molinillo incoming_edges.map(&:origin) end - # @return [Array<Vertex>] the vertices of {#graph} where `self` is a # {#descendent?} def recursive_predecessors - vertices = predecessors - vertices += vertices.map(&:recursive_predecessors).flatten(1) - vertices.uniq! vertices end # @return [Array<Vertex>] the vertices of {#graph} that have an edge with # `self` as their {Edge#origin} @@ -64,14 +76,25 @@ module Gem::Resolver::Molinillo outgoing_edges.map(&:destination) end - # @return [Array<Vertex>] the vertices of {#graph} where `self` is an # {#ancestor?} def recursive_successors - vertices = successors - vertices += vertices.map(&:recursive_successors).flatten(1) - vertices.uniq! vertices end # @return [String] a string suitable for debugging def inspect @@ -107,11 +130,21 @@ module Gem::Resolver::Molinillo # dependency graph? # @return true iff there is a path following edges within this {#graph} def path_to?(other) - equal?(other) || successors.any? { |v| v.path_to?(other) } end alias descendent? path_to? # Is there a path from `other` to `self` following edges in the # dependency graph? # @return true iff there is a path following edges within this {#graph} @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # An error that occurred during the resolution process class ResolverError < StandardError; end @@ -17,7 +18,7 @@ module Gem::Resolver::Molinillo # @param [Array<Object>] required_by @see {#required_by} def initialize(dependency, required_by = []) @dependency = dependency - @required_by = required_by super() end @@ -41,11 +42,11 @@ module Gem::Resolver::Molinillo attr_reader :dependencies # Initializes a new error with the given circular vertices. - # @param [Array<DependencyGraph::Vertex>] nodes the nodes in the dependency # that caused the error - def initialize(nodes) - super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}" - @dependencies = nodes.map(&:payload).to_set end end @@ -55,11 +56,16 @@ module Gem::Resolver::Molinillo # resolution to fail attr_reader :conflicts # Initializes a new error with the given version conflicts. # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} - def initialize(conflicts) pairs = [] - conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting| conflicting.each do |source, conflict_requirements| conflict_requirements.each do |c| pairs << [c, source] @@ -69,7 +75,69 @@ module Gem::Resolver::Molinillo super "Unable to satisfy the following requirements:\n\n" \ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" @conflicts = conflicts end end end @@ -1,5 +1,6 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # The version of Gem::Resolver::Molinillo. - VERSION = '0.5.7'.freeze end @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # Provides information about specifcations and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # Conveys information about the resolution process to a user. module UI @@ -48,7 +49,8 @@ module Gem::Resolver::Molinillo if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) - output.puts debug_info.split("\n").map { |s| ' ' * depth + s } end end @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo class Resolver # A specific resolution from a given {Resolver} @@ -8,22 +9,125 @@ module Gem::Resolver::Molinillo # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict # @attr [Object, nil] existing the existing spec that was in conflict with # the {#possibility} - # @attr [Object] possibility the spec that was unable to be activated due - # to a conflict # @attr [Object] locked_requirement the relevant locking requirement. # @attr [Array<Array<Object>>] requirement_trees the different requirement # trees that led to every requirement for the conflicting name. # @attr [{String=>Object}] activated_by_name the already-activated specs. Conflict = Struct.new( :requirement, :requirements, :existing, - :possibility, :locked_requirement, :requirement_trees, - :activated_by_name ) # @return [SpecificationProvider] the provider that knows about # dependencies, requirements, specifications, versions, etc. attr_reader :specification_provider @@ -64,7 +168,7 @@ module Gem::Resolver::Molinillo start_resolution while state - break unless state.requirements.any? || state.requirement indicate_progress if state.respond_to?(:pop_possibility_state) # DependencyState debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } @@ -78,7 +182,7 @@ module Gem::Resolver::Molinillo process_topmost_state end - activated.freeze ensure end_resolution end @@ -103,12 +207,25 @@ module Gem::Resolver::Molinillo def start_resolution @started_at = Time.now - handle_missing_or_push_dependency_state(initial_state) debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end # Ends the resolution process # @return [void] def end_resolution @@ -121,11 +238,11 @@ module Gem::Resolver::Molinillo debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state end - require 'rubygems/resolver/molinillo/lib/molinillo/state' - require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' - require 'rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state' - require 'rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider' include Gem::Resolver::Molinillo::Delegates::ResolutionState include Gem::Resolver::Molinillo::Delegates::SpecificationProvider @@ -136,9 +253,12 @@ module Gem::Resolver::Molinillo if possibility attempt_to_activate else - create_conflict if state.is_a? PossibilityState - unwind_for_conflict until possibility && state.is_a?(DependencyState) end end # @return [Object] the current possibility that the resolution is trying @@ -153,63 +273,292 @@ module Gem::Resolver::Molinillo states.last end - # Creates the initial state for the resolution, based upon the # {#requested} dependencies - # @return [DependencyState] the initial state for the resolution - def initial_state graph = DependencyGraph.new.tap do |dg| - original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } } dg.tag(:initial_state) end - requirements = sort_dependencies(original_requested, graph, {}) - initial_requirement = requirements.shift - DependencyState.new( - initial_requirement && name_for(initial_requirement), - requirements, - graph, - initial_requirement, - initial_requirement && search_for(initial_requirement), - 0, - {} - ) end # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict - debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" } conflicts.tap do |c| - sliced_states = states.slice!((state_index_for_unwind + 1)..-1) - raise VersionConflict.new(c) unless state activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c index = states.size - 1 @parents_of.each { |_, a| a.reject! { |i| i >= index } } end end - # @return [Integer] The index to which the resolution should unwind in the - # case of conflict. - def state_index_for_unwind - current_requirement = requirement - existing_requirement = requirement_for_existing_name(name) - index = -1 - [current_requirement, existing_requirement].each do |r| - until r.nil? - current_state = find_state_for(r) - if state_any?(current_state) - current_index = states.index(current_state) - index = current_index if current_index > index - break end - r = parent_of(r) end end - index end # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) @@ -219,29 +568,27 @@ module Gem::Resolver::Molinillo parent_state.requirement end # @return [Object] the requirement that led to a version of a possibility # with the given name being activated. def requirement_for_existing_name(name) - return nil unless activated.vertex_named(name).payload states.find { |s| s.name == name }.requirement end # @return [ResolutionState] the state whose `requirement` is the given # `requirement`. def find_state_for(requirement) return nil unless requirement - states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) } - end - - # @return [Boolean] whether or not the given state has any possibilities - # left. - def state_any?(state) - state && state.possibilities.any? end # @return [Conflict] a {Conflict} that reflects the failure to activate # the {#possibility} in conjunction with the current {#state} - def create_conflict vertex = activated.vertex_named(name) locked_requirement = locked_requirement_named(name) @@ -250,18 +597,21 @@ module Gem::Resolver::Molinillo requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements end requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement - vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) } activated_by_name = {} - activated.each { |v| activated_by_name[v.name] = v.payload if v.payload } conflicts[name] = Conflict.new( requirement, requirements, - vertex.payload, possibility, locked_requirement, requirement_trees, - activated_by_name ) end @@ -272,6 +622,7 @@ module Gem::Resolver::Molinillo vertex.requirements.map { |r| requirement_tree_for(r) } end # @return [Array<Object>] the list of requirements that led to # `requirement` being required. def requirement_tree_for(requirement) @@ -311,116 +662,47 @@ module Gem::Resolver::Molinillo # @return [void] def attempt_to_activate debug(depth) { 'Attempting to activate ' + possibility.to_s } - existing_node = activated.vertex_named(name) - if existing_node.payload - debug(depth) { "Found existing spec (#{existing_node.payload})" } - attempt_to_activate_existing_spec(existing_node) else - attempt_to_activate_new_spec - end - end - - # Attempts to activate the current {#possibility} (given that it has - # already been activated) - # @return [void] - def attempt_to_activate_existing_spec(existing_node) - existing_spec = existing_node.payload - if requirement_satisfied_by?(requirement, activated, existing_spec) - new_requirements = requirements.dup - push_state_for_requirements(new_requirements, false) - else - return if attempt_to_swap_possibility - create_conflict - debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" } - unwind_for_conflict - end - end - - # Attempts to swp the current {#possibility} with the already-activated - # spec with the given name - # @return [Boolean] Whether the possibility was swapped into {#activated} - def attempt_to_swap_possibility - activated.tag(:swap) - vertex = activated.vertex_named(name) - activated.set_payload(name, possibility) - if !vertex.requirements. - all? { |r| requirement_satisfied_by?(r, activated, possibility) } || - !new_spec_satisfied? - activated.rewind_to(:swap) - return - end - fixup_swapped_children(vertex) - activate_spec - end - - # Ensures there are no orphaned successors to the given {vertex}. - # @param [DependencyGraph::Vertex] vertex the vertex to fix up. - # @return [void] - def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity - payload = vertex.payload - deps = dependencies_for(payload).group_by(&method(:name_for)) - vertex.outgoing_edges.each do |outgoing_edge| - requirement = outgoing_edge.requirement - parent_index = @parents_of[requirement].last - succ = outgoing_edge.destination - matching_deps = Array(deps[succ.name]) - dep_matched = matching_deps.include?(requirement) - - # only push the current index when it was originally required by the - # same named spec - if parent_index && states[parent_index].name == name - @parents_of[requirement].push(states.size - 1) end - - if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex] - debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" } - succ.requirements.each { |r| @parents_of.delete(r) } - - removed_names = activated.detach_vertex_named(succ.name).map(&:name) - requirements.delete_if do |r| - # the only removed vertices are those with no other requirements, - # so it's safe to delete only based upon name here - removed_names.include?(name_for(r)) - end - elsif !dep_matched - debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" } - # also reset if we're removing the edge, but only if its parent has - # already been fixed up - @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty? - - activated.delete_edge(outgoing_edge) - requirements.delete(requirement) end end end - # Attempts to activate the current {#possibility} (given that it hasn't - # already been activated) # @return [void] - def attempt_to_activate_new_spec - if new_spec_satisfied? - activate_spec else create_conflict unwind_for_conflict end end - # @return [Boolean] whether the current spec is satisfied as a new - # possibility. - def new_spec_satisfied? - unless requirement_satisfied_by?(requirement, activated, possibility) - debug(depth) { 'Unsatisfied by requested spec' } - return false - end - - locked_requirement = locked_requirement_named(name) - - locked_spec_satisfied = !locked_requirement || - requirement_satisfied_by?(locked_requirement, activated, possibility) - debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied - - locked_spec_satisfied end # @param [String] requirement_name the spec name to search for @@ -434,7 +716,7 @@ module Gem::Resolver::Molinillo # Add the current {#possibility} to the dependency graph of the current # {#state} # @return [void] - def activate_spec conflicts.delete(name) debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) @@ -442,14 +724,14 @@ module Gem::Resolver::Molinillo end # Requires the dependencies that the recently activated spec has - # @param [Object] activated_spec the specification that has just been # activated # @return [void] - def require_nested_dependencies_for(activated_spec) - nested_dependencies = dependencies_for(activated_spec) debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| - activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d) parent_index = states.size - 1 parents = @parents_of[d] parents << parent_index if parents.empty? @@ -461,23 +743,82 @@ module Gem::Resolver::Molinillo # Pushes a new {DependencyState} that encapsulates both existing and new # requirements # @param [Array] new_requirements # @return [void] def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort - new_requirement = new_requirements.shift new_name = new_requirement ? name_for(new_requirement) : ''.freeze - possibilities = new_requirement ? search_for(new_requirement) : [] handle_missing_or_push_dependency_state DependencyState.new( new_name, new_requirements, new_activated, - new_requirement, possibilities, depth, conflicts.dup ) end # Pushes a new {DependencyState}. # If the {#specification_provider} says to # {SpecificationProvider#allow_missing?} that particular requirement, and # there are no possibilities for that requirement, then `state` is not - # pushed, and the node in {#activated} is removed, and we continue # resolving the remaining requirements. # @param [DependencyState] state # @return [void] @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph' module Gem::Resolver::Molinillo # This class encapsulates a dependency resolver. @@ -8,7 +9,7 @@ module Gem::Resolver::Molinillo # # class Resolver - require 'rubygems/resolver/molinillo/lib/molinillo/resolution' # @return [SpecificationProvider] the specification provider used # in the resolution process @@ -1,4 +1,5 @@ # frozen_string_literal: true module Gem::Resolver::Molinillo # A state that a {Resolution} can be in # @attr [String] name the name of the current requirement @@ -7,7 +8,8 @@ module Gem::Resolver::Molinillo # @attr [Object] requirement the current requirement # @attr [Object] possibilities the possibilities to satisfy the current requirement # @attr [Integer] depth the depth of the resolution - # @attr [Set<Object>] conflicts unresolved conflicts ResolutionState = Struct.new( :name, :requirements, @@ -15,14 +17,15 @@ module Gem::Resolver::Molinillo :requirement, :possibilities, :depth, - :conflicts ) class ResolutionState # Returns an empty resolution state # @return [ResolutionState] an empty state def self.empty - new(nil, [], DependencyGraph.new, nil, nil, 0, Set.new) end end @@ -40,7 +43,8 @@ module Gem::Resolver::Molinillo requirement, [possibilities.pop], depth + 1, - conflicts.dup ).tap do |state| state.activated.tag(state) end @@ -104,7 +104,7 @@ class Gem::Resolver::Specification # Returns true if this specification is installable on this platform. def installable_platform? - Gem::Platform.match spec.platform end def local? # :nodoc: |