【问题标题】:How to find all referenced files in Ruby如何在 Ruby 中查找所有引用的文件
【发布时间】:2013-03-29 01:56:50
【问题描述】:

我想检测 Ruby 文件出于文档目的而直接引用的所有文件。阅读基本要求列表是不完整的,因为有些文件是可传递导入的,而另一些文件是导入但从未使用过的。例如:

a.rb:

require 'b'
require 'e'
class A; end
B.new; C.new

b.rb:

require 'c'
require 'd'
class B; end
C.new; D.new

c.rb:
class C; end

(d.rb and e.rb are just like c.rb)

那么我想为a.rb 获取的列表是b.rb, c.rb。没有 D 或 E,因为它们没有被直接引用。希望这是有道理的!

【问题讨论】:

  • 排除e.rb和包括c.rb是很难解决的问题(甚至可能相当于“停机问题”,我不确定)。它们本质上需要完整解析 Ruby 代码并理解所有可能的代码路径。您也许可以通过仅查找简单的轻量级依赖项(例如类定义和基本实例化)来使这个准确率达到 90%。那么 90% 的准确率是否可以接受?
  • @NeilSlater 你能加入这里吗?chat.stackoverflow.com/rooms/27184/ruby-conceptual
  • @NeilSlater 是的,90% 甚至更少都可以。

标签: ruby import requires


【解决方案1】:

所以这里对于“使用”的含义有些模糊。显然 d 被使用,因为 b.rb (也被使用)最后调用 D.new 。如果我们警告 'used' 表示“代码是从该文件执行的,而不是在 require 过程中”,那么下面的代码就结束了,因为我可以在 ruby​​ 1.9.3 上获得

require 'set'
def analyze(filename)
  require_depth = 0
  files = Set.new
  set_trace_func( lambda do |event, file, line, id, binding, classname|
    case event
    when 'call'then require_depth += 1 if id == :require && classname == Kernel
    when 'return' then require_depth -= 1 if id == :require && classname == Kernel
    when 'line' 
      files << file if require_depth == 0
    end
  end)
  load filename
  set_trace_func nil
  files.reject {|f| f == __FILE__ || f =~ %r{/lib/ruby/site_ruby}}
end

您可以通过运行analyse 'a.rb' 来使用它(假设所有涉及的文件都在加载路径上)。这样做是使用 ruby​​ 的 set_trace_func 来监听正在发生的事情。第一部分是粗略地尝试忽略调用 require 期间发生的所有事情。然后我们累积每行执行的 ruby​​ 的文件名。最后一行只是清理垃圾(例如补丁所需的 ruby​​gems 文件)。

这实际上不适用于测试示例:当 B.new 运行时,实际上并没有执行 b.rb 中的任何代码行。但是,如果 B(和 C、D 等)具有初始化方法(或调用的某些代码行),那么您应该得到所需的结果。这是非常简单的东西,可能会被各种各样的东西愚弄。特别是如果您在(比如说)B 上调用了一个方法,但该方法的实现不在 b.rb 中(例如使用 attr_accessor 定义的访问器),那么 b.rb 不会被记录

您也许可以更好地使用调用事件,但我认为 set_trace_func 不能做更多事情。

如果您使用的是 ruby​​ 2.0,那么您可以使用 TracePoint,它是 set_trace_func 的替代品。它的语义略有不同,特别是当我们跟踪方法调用时,更容易获取调用它的类,因此

require 'set'
def analyze(filename)
  require_depth = 0
  files = Set.new
  classes_to_files = {}
  trace = TracePoint.new(:call, :line, :return, :c_call, :class) do |tp|
    case tp.event
    when :class
      classes_to_files[tp.self] = tp.path
    when :call, :c_call then 
      if tp.method_id == :require && tp.defined_class == Kernel
        require_depth += 1
      else
        if require_depth == 0
          if path = classes_to_files[tp.self] || classes_to_files[tp.self.class]
            files << path
          end
        end
      end
    when :return then require_depth -= 1 if tp.method_id == :require && tp.defined_class == Kernel
    when :line 
      if require_depth == 0
        files << tp.path 
      end
    end
  end

  trace.enable
  load filename
  trace.disable
  files.reject {|f| f == __FILE__ || f =~ %r{/lib/ruby/site_ruby}}
end

为测试示例返回 a,b,c。它仍然受到基本限制,即它只知道实际执行的代码。

【讨论】:

  • 谢谢!这个想法可行,但我对其进行了一些调整(1.9 版本)——只要(更大)深度为 1,我就在任何事件上添加文件,即在加法之后调用深度,在减法之前返回,并且在深度 = 1 时行.我还在搞砸它。
  • 您指出的动态分析很棘手,但使用全面的测试套件应该可以工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-24
  • 2017-05-13
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多