Commits

Austin Ziegler committed 2204695

Refactored the load of the Python DLL through FFI.

No more hinky require/load hacks; now it's all module_eval hacks.

  • Participants
  • Parent commits 32ec054

Comments (0)

Files changed (11)

File History.rdoc

 === 0.6 / 2011-MM-DD
+* Major Enhancements:
+  * Previous versions of RubyPython theoretically allowed the loading of
+    different Python interpreters during a single Ruby session. Because of the
+    likelihood of crashes, this functionality has been removed. Now, if you
+    attempt to start RubyPython more than once while specifying different
+    Python interpreters, RubyPython will print a warning and continue working
+    with the already loaded interpreter.
+  * The Python interpreter DLL will only be loaded once. It is configured with
+    a self-removing method, Interpreter#infect! instead of require/load reload
+    hacks.
+  * Removed RubyPython.configure; since the interpreter can only be configured
+    once, independent configuration no longer makes sense.
 * Minor Enhancements:
+  * RubyPython interpreter initialization is done with a Mutex synchronization
+    to ensure that only one Python interpreter DLL is loaded.
   * Added RubyPython::Tuple, a simple subclass of ::Array that will correctly
     be converted to a Python tuple object such that isinstance(x, tuple)
     returns True.
+  * Renamed RubyPython::PythonExec to RubyPython::Interpreter. Added some
+    helper methods to assist with comparison. Construction is with an options
+    hash.
+  * The #update methods on Python interpreter observers are now private. This
+    is an implementation detail, not a public interface. (The methods have also
+    been renamed to #python_interpreter_update.)
 * Deprecation:
   * RubyPython's legacy mode (automatic unboxing of Python proxy objects where
     possible) has been deprecated and will be removed in the next non-bugfix
-    release after this version.
+    release after this version. Introduced a new private method
+    (RubyPython.legacy_mode?) to check if legacy mode is turned on so that the
+    deprecation warning is not printed in all uses of RubyPython.
 
 === 0.5.3 / 2011-10-22
 * Bug Fixes:

File lib/rubypython.rb

 #   RubyPython.stop
 module RubyPython
   VERSION = '0.6'
-
-  # Do not load the FFI interface by default. Wait until the user asks for
-  # it.
-  @load_ffi = false
-
-  # Indicates whether the \Python DLL has been loaded. For internal use
-  # only.
-  def self.load_ffi? #:nodoc:
-    @load_ffi
-  end
 end
 
 require 'rubypython/blankobject'
-require 'rubypython/options'
+require 'rubypython/interpreter'
 require 'rubypython/python'
 require 'rubypython/pythonerror'
 require 'rubypython/pyobject'
 require 'rubypython/pymainclass'
 require 'rubypython/pygenerator'
 require 'rubypython/tuple'
+require 'thread'
 
 module RubyPython
   class << self
       @legacy_mode
     end
 
+    def legacy_mode?
+      @legacy_mode = nil unless defined? @legacy_mode
+      @legacy_mode
+    end
+    private :legacy_mode?
+
     def warn_legacy_mode_deprecation
       warn "RubyPython's Legacy Mode is deprecated and will be removed after version #{VERSION}."
     end
     private :warn_legacy_mode_deprecation
 
-    # Starts the \Python interpreter. Either +RubyPython.start+,
-    # +RubyPython.session+, or +RubyPython.run+ must be run before using any
+    ## Starts the \Python interpreter. One of +RubyPython.start+,
+    # RubyPython.session+, or +RubyPython.run+ must be run before using any
     # \Python code. Returns +true+ if the interpreter was started; +false+
     # otherwise.
     #
     #   p sys.version # => "2.7.1"
     #   RubyPython.stop
     def start(options = {})
-      RubyPython.configure(options)
+      Mutex.new.synchronize do
+        # Has the Runtime interpreter been defined?
+        if self.const_defined?(:Runtime, false)
+          # 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."
+          end
+        else
+          self.const_set(:Runtime, RubyPython::Interpreter.new(options))
+        end
 
-      unless @load_ffi
-        @load_ffi = true
-        @reload = false
-        reload_library
+        unless defined? RubyPython::Python.ffi_libraries
+          Runtime.__send__(:infect!, RubyPython::Python)
+        end
       end
 
       return false if RubyPython::Python.Py_IsInitialized != 0
-
-      if @reload
-        reload_library
-        @reload = false
-      end
-
       RubyPython::Python.Py_Initialize
       notify :start
       true
     # run(options = {}) { block to execute in RubyPython context }
     def run(options = {}, &block)
       start(options)
-      module_eval(&block)
+      self.module_eval(&block)
     ensure
       stop
     end
     # This feature is highly experimental and may be disabled in the future.
     def start_from_virtualenv(virtualenv)
       result = start(:python_exe => File.join(virtualenv, "bin", "python"))
-      activate
+      activate_virtualenv
       result
     end
 
-    # Returns an object describing the currently active Python interpreter.
+    # Returns an object describing the active Python interpreter. Returns
+    # +nil+ if there is no active interpreter.
     def python
-      RubyPython::Python::EXEC
+      if self.const_defined? :Runtime
+        self::Runtime
+      else
+        nil
+      end
     end
 
     # Used to activate the virtualenv.
-    def activate
+    def activate_virtualenv
       imp = import("imp")
       imp.load_source("activate_this",
-                      File.join(File.dirname(RubyPython::Python::EXEC.python),
+                      File.join(File.dirname(RubyPython::Runtime.python),
                       "activate_this.py"))
     end
-    private :activate
+    private :activate_virtualenv
 
     def add_observer(object)
       @observers ||= []
       @observers ||= []
       @observers.each do |o|
         next if nil === o
-        o.update status
+        o.__send__ :python_interpreter_update, status
       end
     end
     private :notify

File lib/rubypython/interpreter.rb

+# An instance of this class represents information about a particular
+# \Python interpreter.
+#
+# This class represents the current Python interpreter.
+# A class that represents a \Python executable.
+#
+# End users may get the instance that represents the current running \Python
+# 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
+  # :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)
+  end
+
+  # Based on the name of or path to the \Python executable provided, will
+  # determine:
+  #
+  # * 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
+
+    @version = run_command "import sys; print '%d.%d' % sys.version_info[:2]"
+
+    @dirname = File.dirname(@python)
+    @realname = @python.dup
+    if (@realname !~ /#{@version}$/ and @realname !~ /\.exe$/)
+      @realname = "#{@python}#{@version}"
+    else
+      basename = File.basename(@python, '.exe')
+      @realname = File.join(@dirname, "#{basename}#{@version.gsub(/\./, '')}")
+    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}"
+
+    # 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) ]
+
+    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
+      # 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")
+      # Let's also look in the location that was originally set in this
+      # library:
+      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)
+      end
+    end
+
+    if FFI::Platform.windows?
+      # On Windows, the appropriate DLL is usually be found in
+      # %SYSTEMROOT%\system or %SYSTEMROOT%\system32; as a fallback we'll
+      # 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)
+    end
+
+    # Let's add alternative extensions; again, just in case.
+    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
+    end
+
+    # Remove redundant locations
+    locations.uniq!
+
+    library = nil
+
+    locations.each do |location|
+      if File.exists? location
+        library = location
+        break
+      end
+    end
+
+    library
+  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}>"
+    else
+      "#<invalid interpreter>"
+    end
+  end
+
+  def invalidate!
+    @python = @version = @realname = @sys_prefix = @library = nil
+  end
+  private :invalidate!
+end

File lib/rubypython/operators.rb

     RubyPython::PyMain.cmp(self, other)
   end
 
-  # Called by RubyPython when the interpreter is started or stopped so that the
-  # necessary preparation or cleanup can be done. For internal use only.
-  def self.update(status)
-    case status
-    when :stop
-      @@operator = nil
+  class << self
+    # Called by RubyPython when the interpreter is started or stopped so
+    # that the necessary preparation or cleanup can be done. For internal
+    # use only.
+    def python_interpreter_update(status)
+      case status
+      when :stop
+        @@operator = nil
+      end
     end
+    private :python_interpreter_update
   end
 
   # Aliases eql? to == for Python objects.

File lib/rubypython/options.rb

-require 'ostruct'
-
-module RubyPython
-  # A hash for storing RubyPython execution options.
-  @options = {}
-
-  # A list of options which require the \Python library to be reloaded.
-  NEED_RELOAD = [ :python_exe ] #:nodoc
-  # 20110316 AZ: This option has been removed because it isn't supported in
-  # the current code.
-  # :python_lib -> The full path to the python library you wish to load.
-
-  class << self
-    # Allows one to set options for RubyPython's execution. Parameters may
-    # be set either by supplying a hash argument or by supplying a block and
-    # calling setters on the provided OpenStruct. Returns a copy of the
-    # updated options hash.
-    #
-    # [options] A Hash of options to set.
-    #
-    # The option currently supported is:
-    # [:python_exe] The name of or path to the \Python executable for the
-    # version of \Python you wish to use.
-    # 
-    #   RubyPython.run do
-    #     RubyPython.import('sys').version.rubify.to_f # => 2.7
-    #   end
-    #
-    #   RubyPython.configure :python_exe => 'python2.6'
-    #   # => { :python_exe => "python2.6" }
-    #   RubyPython.run do
-    #     RubyPython.import('sys').version.rubify.to_f # => 2.6
-    #   end
-    #   
-    # The options hash can also be passed directly to +RubyPython.start+,
-    # +RubyPython.session+, or +RubyPython.run+.
-    def configure(options = {})
-      old_values = Hash[*@options.select { |k, v| NEED_RELOAD.include? k }]
-
-      if block_given?
-        ostruct = OpenStruct.new @options
-        yield ostruct
-        olist = ostruct.instance_variable_get('@table').map { |k, v| [ k.to_sym, v ] }
-        @options = Hash[*olist]
-      end
-      @options.merge!(options)
-
-      @reload = true if NEED_RELOAD.any? { |k| @options[k] != old_values[k] } 
-      options
-    end
-
-    # Returns a copy of the hash currently being used to determine run
-    # options. This allows the user to determine what options have been set.
-    # Modification of options should be done via the configure method.
-    def options
-      @options.dup
-    end
-
-    # Reset the options hash.
-    # @return [void]
-    def clear_options
-      @reload = @options.keys.any? { |k| NEED_RELOAD.include? k }
-      @options.clear
-    end
-  end
-end

File lib/rubypython/pymainclass.rb

     end
 
     # Called by RubyPython when the interpreter is started or stopped so
-    # that the neccesary preperation or cleanup can be done. For internal
+    # that the neccesary preparation or cleanup can be done. For internal
     # use only.
-    def update(status)
+    def python_interpreter_update(status)
       case status
       when :stop
         @main = nil
         @builtin = nil
       end
     end
+    private :python_interpreter_update
   end
 
   # The accessible instance of PyMainClass.

File lib/rubypython/pyobject.rb

       # Called by RubyPython when the interpreter is started or stopped so
       # that the necessary preparation or cleanup can be done. For internal
       # use only.
-      def update(status)
+      def python_interpreter_update(status)
         case status
         when :stop
           current_pointers.clear
         end
       end
+      private :python_interpreter_update
     end
 
     self.current_pointers = {}

File lib/rubypython/python.rb

 require 'ffi'
-require 'rubypython/options'
-require 'rubypython/pythonexec'
+require 'thread'
+require 'rubypython/interpreter'
 
 module RubyPython
-  unless defined? PYTHON_RB
-    PYTHON_RB = __FILE__
-    PYTHON_RB.freeze
+  # This module will hold the loaded RubyPython interpreter.
+  module Python #:nodoc: all
   end
-
-  module Python; end
 end
 
-if RubyPython.load_ffi?
-  # This module provides access to the \Python C API functions via the Ruby
-  # FFI gem.
-  #
-  # === Documentation
-  # * {Python C API}[http://docs.python.org/c-api/]
-  # * {Ruby FFI}[http://rdoc.info/projects/ffi/ffi]
-  module RubyPython::Python # :nodoc: all
-    extend FFI::Library
+class RubyPython::Interpreter
+  # Infects the provided module with the Python FFI. Once a single module
+  # has been infected, the #infect! method is removed from
+  # RubyPython::Interpreter.
+  def infect!(mod)
+    Mutex.new.synchronize do
+      self.class.class_eval do
+        undef :infect!
+      end
 
-    EXEC = RubyPython::PythonExec.new(RubyPython.options[:python_exe])
+      mod.extend FFI::Library
+      # FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
+      mod.ffi_lib_flags :lazy, :global
+      mod.ffi_lib self.library
 
-    PYTHON_LIB = EXEC.library
-    # FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
-    ffi_lib_flags :lazy, :global
-    ffi_lib EXEC.library
+      # This class is a little bit of a hack to extract the address of
+      # global structs. If someone knows a better way please let me know.
+      mod.module_eval do
+        self.const_set :DummyStruct, Class.new(FFI::Struct)
+        self::DummyStruct.layout :dummy_var, :int
 
-    # The class is a little bit of a hack to extract the address of global
-    # structs. If someone knows a better way please let me know.
-    class DummyStruct < FFI::Struct # :nodoc:
-      layout :dummy_var, :int
-    end
+        self.const_set(:PY_FILE_INPUT, 257)
+        self.const_set(:PY_EVAL_INPUT, 258)
+        self.const_set(:METH_VARARGS, 0x0001)
 
-    PY_FILE_INPUT = 257
-    PY_EVAL_INPUT = 258
+        # Function methods & constants
+        attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
+        callback :PyCFunction, [:pointer, :pointer], :pointer
 
-    # Function methods & constants
-    METH_VARARGS = 0x0001
-    attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
-    callback :PyCFunction, [:pointer, :pointer], :pointer
+        attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
+        attach_function :PyRun_SimpleString, [:string], :pointer
+        attach_function :Py_CompileString, [:string, :string, :int], :pointer
+        attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
+        attach_function :PyErr_SetString, [:pointer, :string], :void
 
-    attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
-    attach_function :PyRun_SimpleString, [:string], :pointer
-    attach_function :Py_CompileString, [:string, :string, :int], :pointer
-    attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
-    attach_function :PyErr_SetString, [:pointer, :string], :void
+        # Python interpreter startup and shutdown
+        attach_function :Py_IsInitialized, [], :int
+        attach_function :Py_Initialize, [], :void
+        attach_function :Py_Finalize, [], :void
 
-    # Python interpreter startup and shutdown
-    attach_function :Py_IsInitialized, [], :int
-    attach_function :Py_Initialize, [], :void
-    attach_function :Py_Finalize, [], :void
+        # Module methods
+        attach_function :PyImport_ImportModule, [:string], :pointer
 
-    # Module methods
-    attach_function :PyImport_ImportModule, [:string], :pointer
+        # Object Methods
+        attach_function :PyObject_HasAttrString, [:pointer, :string], :int
+        attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
+        attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
+        attach_function :PyObject_Dir, [:pointer], :pointer
 
-    # Object Methods
-    attach_function :PyObject_HasAttrString, [:pointer, :string], :int
-    attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
-    attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
-    attach_function :PyObject_Dir, [:pointer], :pointer
+        attach_function :PyObject_Compare, [:pointer, :pointer], :int
 
-    attach_function :PyObject_Compare, [:pointer, :pointer], :int
+        attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
+        attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
+        attach_function :PyCallable_Check, [:pointer], :int
 
-    attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
-    attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
-    attach_function :PyCallable_Check, [:pointer], :int
+        ### Python To Ruby Conversion
+        # String Methods
+        attach_function :PyString_AsString, [:pointer], :string
+        attach_function :PyString_FromString, [:string], :pointer
+        attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
+        attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer
 
-    ### Python To Ruby Conversion
-    # String Methods
-    attach_function :PyString_AsString, [:pointer], :string
-    attach_function :PyString_FromString, [:string], :pointer
-    attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
-    attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer
+        # List Methods
+        attach_function :PyList_GetItem, [:pointer, :int], :pointer
+        attach_function :PyList_Size, [:pointer], :int
+        attach_function :PyList_New, [:int], :pointer
+        attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void
 
-    # List Methods
-    attach_function :PyList_GetItem, [:pointer, :int], :pointer
-    attach_function :PyList_Size, [:pointer], :int
-    attach_function :PyList_New, [:int], :pointer
-    attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void
+        # Integer Methods
+        attach_function :PyInt_AsLong, [:pointer], :long
+        attach_function :PyInt_FromLong, [:long], :pointer
 
-    # Integer Methods
-    attach_function :PyInt_AsLong, [:pointer], :long
-    attach_function :PyInt_FromLong, [:long], :pointer
+        attach_function :PyLong_AsLong, [:pointer], :long
+        attach_function :PyLong_FromLong, [:pointer], :long
 
-    attach_function :PyLong_AsLong, [:pointer], :long
-    attach_function :PyLong_FromLong, [:pointer], :long
+        # Float Methods
+        attach_function :PyFloat_AsDouble, [:pointer], :double
+        attach_function :PyFloat_FromDouble, [:double], :pointer
 
-    # Float Methods
-    attach_function :PyFloat_AsDouble, [:pointer], :double
-    attach_function :PyFloat_FromDouble, [:double], :pointer
+        # Tuple Methods
+        attach_function :PySequence_List, [:pointer], :pointer
+        attach_function :PySequence_Tuple, [:pointer], :pointer
+        attach_function :PyTuple_Pack, [:int, :varargs], :pointer
 
-    # Tuple Methods
-    attach_function :PySequence_List, [:pointer], :pointer
-    attach_function :PySequence_Tuple, [:pointer], :pointer
-    attach_function :PyTuple_Pack, [:int, :varargs], :pointer
+        # Dict/Hash Methods
+        attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
+        attach_function :PyDict_New, [], :pointer
+        attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
+        attach_function :PyDict_Contains, [:pointer, :pointer], :int
+        attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer
 
-    # Dict/Hash Methods
-    attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
-    attach_function :PyDict_New, [], :pointer
-    attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
-    attach_function :PyDict_Contains, [:pointer, :pointer], :int
-    attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer
+        # Error Methods
+        attach_variable :PyExc_Exception, self::DummyStruct.by_ref
+        attach_variable :PyExc_StopIteration, self::DummyStruct.by_ref
+        attach_function :PyErr_SetNone, [:pointer], :void
+        attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
+        attach_function :PyErr_Occurred, [], :pointer
+        attach_function :PyErr_Clear, [], :void
 
-    # Error Methods
-    attach_variable :PyExc_Exception, DummyStruct.by_ref
-    attach_variable :PyExc_StopIteration, DummyStruct.by_ref
-    attach_function :PyErr_SetNone, [:pointer], :void
-    attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
-    attach_function :PyErr_Occurred, [], :pointer
-    attach_function :PyErr_Clear, [], :void
+        # Reference Counting
+        attach_function :Py_IncRef, [:pointer], :void
+        attach_function :Py_DecRef, [:pointer], :void
 
-    # Reference Counting
-    attach_function :Py_IncRef, [:pointer], :void
-    attach_function :Py_DecRef, [:pointer], :void
+        # Type Objects
+        # attach_variable :PyBaseObject_Type, self::DummyStruct.by_value # built-in 'object' 
+        # attach_variable :PyBaseString_Type, self::DummyStruct.by_value
+        # attach_variable :PyBool_Type, self::DummyStruct.by_value
+        # attach_variable :PyBuffer_Type, self::DummyStruct.by_value
+        # attach_variable :PyByteArrayIter_Type, self::DummyStruct.by_value
+        # attach_variable :PyByteArray_Type, self::DummyStruct.by_value
+        attach_variable :PyCFunction_Type, self::DummyStruct.by_value
+        # attach_variable :PyCObject_Type, self::DummyStruct.by_value
+        # attach_variable :PyCallIter_Type, self::DummyStruct.by_value
+        # attach_variable :PyCapsule_Type, self::DummyStruct.by_value
+        # attach_variable :PyCell_Type, self::DummyStruct.by_value
+        # attach_variable :PyClassMethod_Type, self::DummyStruct.by_value
+        attach_variable :PyClass_Type, self::DummyStruct.by_value
+        # attach_variable :PyCode_Type, self::DummyStruct.by_value
+        # attach_variable :PyComplex_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictItems_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictIterItem_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictIterKey_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictIterValue_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictKeys_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictProxy_Type, self::DummyStruct.by_value
+        # attach_variable :PyDictValues_Type, self::DummyStruct.by_value
+        attach_variable :PyDict_Type, self::DummyStruct.by_value
+        # attach_variable :PyEllipsis_Type, self::DummyStruct.by_value
+        # attach_variable :PyEnum_Type, self::DummyStruct.by_value
+        # attach_variable :PyFile_Type, self::DummyStruct.by_value
+        attach_variable :PyFloat_Type, self::DummyStruct.by_value
+        # attach_variable :PyFrame_Type, self::DummyStruct.by_value
+        # attach_variable :PyFrozenSet_Type, self::DummyStruct.by_value
+        attach_variable :PyFunction_Type, self::DummyStruct.by_value
+        # attach_variable :PyGen_Type, self::DummyStruct.by_value
+        # attach_variable :PyGetSetDescr_Type, self::DummyStruct.by_value
+        # attach_variable :PyInstance_Type, self::DummyStruct.by_value
+        attach_variable :PyInt_Type, self::DummyStruct.by_value
+        attach_variable :PyList_Type, self::DummyStruct.by_value
+        attach_variable :PyLong_Type, self::DummyStruct.by_value
+        # attach_variable :PyMemberDescr_Type, self::DummyStruct.by_value
+        # attach_variable :PyMemoryView_Type, self::DummyStruct.by_value
+        attach_variable :PyMethod_Type, self::DummyStruct.by_value
+        # attach_variable :PyModule_Type, self::DummyStruct.by_value
+        # attach_variable :PyNullImporter_Type, self::DummyStruct.by_value
+        # attach_variable :PyProperty_Type, self::DummyStruct.by_value
+        # attach_variable :PyRange_Type, self::DummyStruct.by_value
+        # attach_variable :PyReversed_Type, self::DummyStruct.by_value
+        # attach_variable :PySTEntry_Type, self::DummyStruct.by_value
+        # attach_variable :PySeqIter_Type, self::DummyStruct.by_value
+        # attach_variable :PySet_Type, self::DummyStruct.by_value
+        # attach_variable :PySlice_Type, self::DummyStruct.by_value
+        # attach_variable :PyStaticMethod_Type, self::DummyStruct.by_value
+        attach_variable :PyString_Type, self::DummyStruct.by_value
+        # attach_variable :PySuper_Type, self::DummyStruct.by_value # built-in 'super' 
+        # attach_variable :PyTraceBack_Type, self::DummyStruct.by_value
+        attach_variable :PyTuple_Type, self::DummyStruct.by_value
+        attach_variable :PyType_Type, self::DummyStruct.by_value
+        # attach_variable :PyUnicode_Type, self::DummyStruct.by_value
+        # attach_variable :PyWrapperDescr_Type, self::DummyStruct.by_value
 
-    # Type Objects
-    # attach_variable :PyBaseObject_Type, DummyStruct.by_value # built-in 'object' 
-    # attach_variable :PyBaseString_Type, DummyStruct.by_value
-    # attach_variable :PyBool_Type, DummyStruct.by_value
-    # attach_variable :PyBuffer_Type, DummyStruct.by_value
-    # attach_variable :PyByteArrayIter_Type, DummyStruct.by_value
-    # attach_variable :PyByteArray_Type, DummyStruct.by_value
-    attach_variable :PyCFunction_Type, DummyStruct.by_value
-    # attach_variable :PyCObject_Type, DummyStruct.by_value
-    # attach_variable :PyCallIter_Type, DummyStruct.by_value
-    # attach_variable :PyCapsule_Type, DummyStruct.by_value
-    # attach_variable :PyCell_Type, DummyStruct.by_value
-    # attach_variable :PyClassMethod_Type, DummyStruct.by_value
-    attach_variable :PyClass_Type, DummyStruct.by_value
-    # attach_variable :PyCode_Type, DummyStruct.by_value
-    # attach_variable :PyComplex_Type, DummyStruct.by_value
-    # attach_variable :PyDictItems_Type, DummyStruct.by_value
-    # attach_variable :PyDictIterItem_Type, DummyStruct.by_value
-    # attach_variable :PyDictIterKey_Type, DummyStruct.by_value
-    # attach_variable :PyDictIterValue_Type, DummyStruct.by_value
-    # attach_variable :PyDictKeys_Type, DummyStruct.by_value
-    # attach_variable :PyDictProxy_Type, DummyStruct.by_value
-    # attach_variable :PyDictValues_Type, DummyStruct.by_value
-    attach_variable :PyDict_Type, DummyStruct.by_value
-    # attach_variable :PyEllipsis_Type, DummyStruct.by_value
-    # attach_variable :PyEnum_Type, DummyStruct.by_value
-    # attach_variable :PyFile_Type, DummyStruct.by_value
-    attach_variable :PyFloat_Type, DummyStruct.by_value
-    # attach_variable :PyFrame_Type, DummyStruct.by_value
-    # attach_variable :PyFrozenSet_Type, DummyStruct.by_value
-    attach_variable :PyFunction_Type, DummyStruct.by_value
-    # attach_variable :PyGen_Type, DummyStruct.by_value
-    # attach_variable :PyGetSetDescr_Type, DummyStruct.by_value
-    # attach_variable :PyInstance_Type, DummyStruct.by_value
-    attach_variable :PyInt_Type, DummyStruct.by_value
-    attach_variable :PyList_Type, DummyStruct.by_value
-    attach_variable :PyLong_Type, DummyStruct.by_value
-    # attach_variable :PyMemberDescr_Type, DummyStruct.by_value
-    # attach_variable :PyMemoryView_Type, DummyStruct.by_value
-    attach_variable :PyMethod_Type, DummyStruct.by_value
-    # attach_variable :PyModule_Type, DummyStruct.by_value
-    # attach_variable :PyNullImporter_Type, DummyStruct.by_value
-    # attach_variable :PyProperty_Type, DummyStruct.by_value
-    # attach_variable :PyRange_Type, DummyStruct.by_value
-    # attach_variable :PyReversed_Type, DummyStruct.by_value
-    # attach_variable :PySTEntry_Type, DummyStruct.by_value
-    # attach_variable :PySeqIter_Type, DummyStruct.by_value
-    # attach_variable :PySet_Type, DummyStruct.by_value
-    # attach_variable :PySlice_Type, DummyStruct.by_value
-    # attach_variable :PyStaticMethod_Type, DummyStruct.by_value
-    attach_variable :PyString_Type, DummyStruct.by_value
-    # attach_variable :PySuper_Type, DummyStruct.by_value # built-in 'super' 
-    # attach_variable :PyTraceBack_Type, DummyStruct.by_value
-    attach_variable :PyTuple_Type, DummyStruct.by_value
-    attach_variable :PyType_Type, DummyStruct.by_value
-    # attach_variable :PyUnicode_Type, DummyStruct.by_value
-    # attach_variable :PyWrapperDescr_Type, DummyStruct.by_value
+        attach_variable :Py_TrueStruct, :_Py_TrueStruct, self::DummyStruct.by_value
+        attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, self::DummyStruct.by_value
+        attach_variable :Py_NoneStruct, :_Py_NoneStruct, self::DummyStruct.by_value
 
-    attach_variable :Py_TrueStruct, :_Py_TrueStruct, DummyStruct.by_value
-    attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, DummyStruct.by_value
-    attach_variable :Py_NoneStruct, :_Py_NoneStruct, DummyStruct.by_value
+        # This is an implementation of the basic structure of a Python PyObject
+        # struct. The C struct is actually much larger, but since we only access
+        # the first two data members via FFI and always deal with struct
+        # pointers there is no need to mess around with the rest of the object.
+        self.const_set :PyObjectStruct, Class.new(FFI::Struct)
+        self::PyObjectStruct.layout :ob_refcnt, :ssize_t,
+          :ob_type, :pointer
 
-    # This is an implementation of the basic structure of a Python PyObject
-    # struct. The C struct is actually much larger, but since we only access
-    # the first two data members via FFI and always deal with struct
-    # pointers there is no need to mess around with the rest of the object.
-    class PyObjectStruct < FFI::Struct
-      layout :ob_refcnt, :ssize_t,
-             :ob_type, :pointer
-    end
+        # This struct is used when defining Python methods.
+        self.const_set :PyMethodDef, Class.new(FFI::Struct)
+        self::PyMethodDef.layout :ml_name, :pointer,
+          :ml_meth, :PyCFunction,
+          :ml_flags, :int,
+          :ml_doc, :pointer
+      end
 
-    # This struct is used when defining Python methods.
-    class PyMethodDef < FFI::Struct
-      layout :ml_name, :pointer,
-             :ml_meth, :PyCFunction,
-             :ml_flags, :int,
-             :ml_doc, :pointer
     end
   end
+  private :infect!
 end

File lib/rubypython/pythonexec.rb

-# A class that represents a \Python executable.
-#
-# End users may get the instance that represents the current running \Python
-# interpreter (from +RubyPython.python+), but should not directly
-# instantiate this class.
-class RubyPython::PythonExec
-  # Based on the name of or path to the \Python executable provided, will
-  # determine:
-  #
-  # * 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(python_executable)
-    @python = python_executable || "python"
-    @python = %x(#{@python} -c "import sys; print sys.executable").chomp
-
-    @version = run_command "import sys; print '%d.%d' % sys.version_info[:2]"
-
-    @dirname = File.dirname(@python)
-    @realname = @python.dup
-    if (@realname !~ /#{@version}$/ and @realname !~ /\.exe$/)
-      @realname = "#{@python}#{@version}"
-    else
-      basename = File.basename(@python, '.exe')
-      @realname = File.join(@dirname, "#{basename}#{@version.gsub(/\./, '')}")
-    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}"
-
-    # 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) ]
-
-    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
-      # 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")
-      # Let's also look in the location that was originally set in this
-      # library:
-      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)
-      end
-    end
-
-    if FFI::Platform.windows?
-      # On Windows, the appropriate DLL is usually be found in
-      # %SYSTEMROOT%\system or %SYSTEMROOT%\system32; as a fallback we'll
-      # 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)
-    end
-
-    # Let's add alternative extensions; again, just in case.
-    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
-    end
-
-    # Remove redundant locations
-    locations.uniq!
-
-    library = nil
-
-    locations.each do |location|
-      if File.exists? location
-        library = location
-        break
-      end
-    end
-
-    library
-  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
-
-  # 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}>"
-    else
-      "#<invalid interpreter>"
-    end
-  end
-
-  def invalidate!
-    @python = @version = @realname = @sys_prefix = @library = nil
-  end
-  private :invalidate!
-end

File lib/rubypython/rubypyproxy.rb

     def _wrap(pyobject)
       if pyobject.class?
         RubyPyClass.new(pyobject)
-      elsif RubyPython.legacy_mode
+      elsif RubyPython.__send__ :legacy_mode?
         pyobject.rubify
       else
         RubyPyProxy.new(pyobject)

File spec/rubypython_spec.rb

       RubyPython.stop.should be_false
     end
   end
-
-  describe '#reload_library', :slow => true do
-    it 'leaves RubyPython in a stable state' do
-      lambda do 
-        RubyPython.instance_eval { reload_library }
-        RubyPython.run {}
-      end.should_not raise_exception
-    end
-  end
-
-  describe '.configure', :slow => true do
-    it 'allows python executable to be specified', :unless => `which python2.6`.empty? do
-      RubyPython.configure :python_exe => 'python2.6'
-      RubyPython.run do
-        sys = RubyPython.import 'sys'
-        sys.version.rubify.to_f.should == 2.6
-      end
-    end
-
-    after(:all) do
-      RubyPython.clear_options
-      RubyPython.instance_eval { reload_library }
-    end
-  end
 end