Commits

Austin Ziegler committed de6dbc7

Improving the debuggaility of RubyPython::Interpreter.

Comments (0)

Files changed (2)

           # If this constant is defined, then yes it is. Since it is, let's
           # see if we should print a warning to the user.
           unless Runtime == options
-            warn "The Python interpreter has already been loaded from #{Runtime.basename} and cannot be changed in this process. Continuing with the current runtime."
+            warn "The Python interpreter has already been loaded from #{Runtime.python} and cannot be changed in this process. Continuing with the current runtime."
           end
         else
           self.const_set(:Runtime, RubyPython::Interpreter.new(options))

lib/rubypython/interpreter.rb

+# -*- ruby encoding: utf-8 -*-
+
+##
 # An instance of this class represents information about a particular
 # \Python interpreter.
 #
 # interpreter (from +RubyPython.python+), but should not directly
 # instantiate this class.
 class RubyPython::Interpreter
+  ##
   # Compare the current Interpreter to the provided Interpreter or
-  # configuration hash. If comparing against a configuration hash, only the
+  # configuration hash. A configuration hash will be converted to an
+  # Interpreter object before being compared.
   # :python_exe basename is used. If comparing against another Interpreter
   # object, the Interpreter basename and version are used.
   def ==(other)
     other = self.class.new(other) if other.kind_of? Hash
     return false unless other.kind_of? self.class
-    (self.version == other.version) && (self.basename == other.basename)
+    (self.version == other.version) && (self.version_name == other.version_name)
   end
 
-  # Based on the name of or path to the \Python executable provided, will
-  # determine:
+  ##
+  # Create a new instance of an Interpreter instance describing a particular
+  # \Python executable and shared library.
   #
-  # * The full path to the \Python executable.
-  # * The version of \Python being run.
-  # * The system prefix.
-  # * The main loadable \Python library for this version.
-  def initialize(options)
-    @python = options[:python_exe] || "python"
-    @python = %x(#{@python} -c "import sys; print sys.executable").chomp
+  # Expects a hash that matches the configuration options provided to
+  # RubyPython.start; currently only one value is recognized in that hash:
+  #
+  # * <tt>:python_exe</tt>: Specifies the name of the python executable to
+  #   run.
+  def initialize(options = {})
+    @python_exe = options[:python_exe]
+    # Windows: 'C:\\Python27\python.exe'
+    # Mac OS X: '/usr/bin/
+    @python     = runpy "import sys; print sys.executable"
+    @version    = runpy "import sys; print '%d.%d' % sys.version_info[:2]"
+    @sys_prefix = runpy "import sys; print sys.prefix"
 
-    @version = run_command "import sys; print '%d.%d' % sys.version_info[:2]"
+    if FFI::Platform.windows?
+      flat_version  = @version.tr('.', '')
+      basename      = File.basename(@python, '.exe')
 
-    @dirname = File.dirname(@python)
-    @realname = @python.dup
-    if (@realname !~ /#{@version}$/ and @realname !~ /\.exe$/)
-      @realname = "#{@python}#{@version}"
+      if basename =~ /(?:#{@version}|#{flat_version})$/
+        @version_name = basename
+      else
+        @version_name = "#{basename}#{flat_version}"
+      end
     else
-      basename = File.basename(@python, '.exe')
-      @realname = File.join(@dirname, "#{basename}#{@version.gsub(/\./, '')}")
+      basename = File.basename(@python)
+
+      if basename =~ /#{@version}/
+        @version_name = basename
+      else
+        @version_name = "#{basename}#{@version}"
+      end
     end
-    @basename = File.basename(@realname)
 
-    @sys_prefix = run_command 'import sys; print sys.prefix'
     @library = find_python_lib
   end
 
   def find_python_lib
     # By default, the library name will be something like
     # libpython2.6.so, but that won't always work.
-    libbase = "#{FFI::Platform::LIBPREFIX}#{@basename}"
-    libext = FFI::Platform::LIBSUFFIX
-    libname = "#{libbase}.#{libext}"
+    @libbase = "#{FFI::Platform::LIBPREFIX}#{@version_name}"
+    @libext = FFI::Platform::LIBSUFFIX
+    @libname = "#{@libbase}.#{@libext}"
 
     # We may need to look in multiple locations for Python, so let's
     # build this as an array.
-    locations = [ File.join(@sys_prefix, "lib", libname) ]
+    @locations = [ File.join(@sys_prefix, "lib", @libname) ]
 
     if FFI::Platform.mac?
       # On the Mac, let's add a special case that has even a different
-      # libname. This may not be fully useful on future versions of OS
+      # @libname. This may not be fully useful on future versions of OS
       # X, but it should work on 10.5 and 10.6. Even if it doesn't, the
       # next step will (/usr/lib/libpython<version>.dylib is a symlink
       # to the correct location).
-      locations << File.join(@sys_prefix, "Python")
+      @locations << File.join(@sys_prefix, "Python")
       # Let's also look in the location that was originally set in this
       # library:
-      File.join(@sys_prefix, "lib", "#{@realname}", "config", libname)
+      File.join(@sys_prefix, "lib", "#{@realname}", "config", @libname)
     end
 
     if FFI::Platform.unix?
       # On Unixes, let's look in some standard alternative places, too.
       # Just in case. Some Unixes don't include a .so symlink when they
       # should, so let's look for the base case of .so.1, too.
-      [ libname, "#{libname}.1" ].each do |name|
-        locations << File.join("/opt/local/lib", name)
-        locations << File.join("/opt/lib", name)
-        locations << File.join("/usr/local/lib", name)
-        locations << File.join("/usr/lib", name)
-        locations << File.join("/opt/local/lib64", name)
-        locations << File.join("/opt/lib64", name)
-        locations << File.join("/usr/local/lib64", name)
-        locations << File.join("/usr/lib64", name)
+      [ @libname, "#{@libname}.1" ].each do |name|
+        @locations << File.join("/opt/local/lib", name)
+        @locations << File.join("/opt/lib", name)
+        @locations << File.join("/usr/local/lib", name)
+        @locations << File.join("/usr/lib", name)
+        @locations << File.join("/opt/local/lib64", name)
+        @locations << File.join("/opt/lib64", name)
+        @locations << File.join("/usr/local/lib64", name)
+        @locations << File.join("/usr/lib64", name)
       end
     end
 
       # use C:\Windows\system{,32} as well as the install directory and the
       # install directory + libs.
       system_root = File.expand_path(ENV['SYSTEMROOT']).gsub(/\\/, '/')
-      locations << File.join(system_root, 'system', libname)
-      locations << File.join(system_root, 'system32', libname)
-      locations << File.join("C:/WINDOWS", "System", libname)
-      locations << File.join("C:/WINDOWS", "System32", libname)
-      locations << File.join(@dirname, libname)
-      locations << File.join(@dirname, 'libs', libname)
+      @locations << File.join(system_root, 'system', @libname)
+      @locations << File.join(system_root, 'system32', @libname)
+      @locations << File.join("C:/WINDOWS", "System", @libname)
+      @locations << File.join("C:/WINDOWS", "System32", @libname)
+      @locations << File.join(sys_prefix, @libname)
+      @locations << File.join(sys_prefix, 'libs', @libname)
     end
 
     # Let's add alternative extensions; again, just in case.
-    locations.dup.each do |location|
+    @locations.dup.each do |location|
       path = File.dirname(location)
-      base = File.basename(location, ".#{libext}")
-      locations << File.join(path, "#{base}.so")    # Standard Unix
-      locations << File.join(path, "#{base}.dylib") # Mac OS X
-      locations << File.join(path, "#{base}.dll")   # Windows
-      locations << File.join(path, "#{base}.a")     # Non-DLL
+      base = File.basename(location, ".#{@libext}")
+      @locations << File.join(path, "#{base}.so")    # Standard Unix
+      @locations << File.join(path, "#{base}.dylib") # Mac OS X
+      @locations << File.join(path, "#{base}.dll")   # Windows
     end
 
     # Remove redundant locations
-    locations.uniq!
+    @locations.uniq!
 
     library = nil
 
-    locations.each do |location|
+    @locations.each do |location|
       if File.exists? location
         library = location
         break
   end
   private :find_python_lib
 
-  # The python executable to use.
-  attr_reader :python
-  # The real name of the python executable (with version).
-  attr_reader :realname
-  # The sys.prefix for Python.
-  attr_reader :sys_prefix
-  # The Python library.
-  attr_reader :library
-  # The Python version
-  attr_reader :version
-  # The basename of the Python interpreter
-  attr_reader :basename
-
-  # Run a Python command-line command.
-  def run_command(command)
-    %x(#{@python} -c "#{command}").chomp if @python
-  end
-
-  def to_s
-    @realname
-  end
-
-  def inspect
-    if @python
-      "#<#{realname} #{sys_prefix}>"
+  def valid?
+    if @python.nil? or @python.empty?
+      false
+    elsif @library.nil? or @library.empty?
+      false
     else
-      "#<invalid interpreter>"
+      true
     end
   end
 
-  def invalidate!
-    @python = @version = @realname = @sys_prefix = @library = nil
+  ##
+  # The name of the \Python executable that is used. This is the value of
+  # 'sys.executable' for the \Python interpreter provided in
+  # <tt>:python_exe</tt> or 'python' if it is not provided.
+  #
+  # On Mac OS X Lion (10.7), this value is '/usr/bin/python' for 'python'.
+  attr_reader :python
+  ##
+  # The version of the \Python interpreter. This is a decimalized version of
+  # 'sys.version_info[:2]' (such that \Python 2.7.1 is reported as '2.7').
+  attr_reader :version
+  ##
+  # The system prefix for the \Python interpreter. This is the value of
+  # 'sys.prefix'.
+  attr_reader :sys_prefix
+  ##
+  # The basename of the \Python interpreter with a version number. This is
+  # mostly an intermediate value used to find the shared \Python library,
+  # but /usr/bin/python is often a link to /usr/bin/python2.7 so it may be
+  # of value. Note that this does *not* include the full path to the
+  # interpreter.
+  attr_reader :version_name
+
+  # The \Python library.
+  attr_reader :library
+
+  # Run a Python command-line command.
+  def runpy(command)
+    %x(#{@python || @python_exe || 'python'} -c "#{command}").chomp
   end
-  private :invalidate!
+  private :runpy
+
+  def inspect(debug = false)
+    if debug
+      debug_s
+    elsif @python
+      "#<#{self.class}: #{python} v#{version} #{sys_prefix} #{version_name}>"
+    else
+      "#<#{self.class}: invalid interpreter>"
+    end
+  end
+
+  def debug_s(format = nil)
+    system = ""
+    system << "windows " if FFI::Platform.windows?
+    system << "mac " if FFI::Platform.mac?
+    system << "unix " if FFI::Platform.unix?
+    system << "unknown " if system.empty?
+
+    case format
+    when :report
+      s = <<-EOS
+python_exe:   #{@python_exe}
+python:       #{@python}
+version:      #{@version}
+sys_prefix:   #{@sys_prefix}
+version_name: #{@version_name}
+platform:     #{system.chomp}
+library:      #{@library.inspect}
+  libbase:    #{@libbase}
+  libext:     #{@libext}
+  libname:    #{@libname}
+  locations:  #{@locations.inspect}
+      EOS
+    else
+      s = "#<#{self.class}: "
+      s << "python_exe=#{@python_exe.inspect} "
+      s << "python=#{@python.inspect} "
+      s << "version=#{@version.inspect} "
+      s << "sys_prefix=#{@sys_prefix.inspect} "
+      s << "version_name=#{@version_name.inspect} "
+      s << system
+      s << "library=#{@library.inspect} "
+      s << "libbase=#{@libbase.inspect} "
+      s << "libext=#{@libext.inspect} "
+      s << "libname=#{@libname.inspect} "
+      s << "locations=#{@locations.inspect}"
+    end
+
+    s
+  end
 end