Report abuse


			
# depends on ffi.rb

# All things platform-dependent.
#
# The platforms Rubinius runs on can be abstracted into a rough hierarchy.
# For example, all BSDs and Linux variants can be thought of as being in
# the POSIX family of operating systems. The BSDs then may further break
# down to the various distributions, those into their separate version
# numbers and and so on. This is represented by an inheritance hierarchy
# starting from the "abstract" Platform::Base and descending into various
# subtrees. For example, the class ultimately implementing OpenBSD 4.0
# would look something like OpenBSD40 < OpenBSD < BSD < POSIX < Base.
#
# Each platform is responsible for determining whether it matches the
# one Rubinius is currently running on. This is naturally also tiered.
#
# Each step along the way can either define additional behaviour or then
# remove behaviour that a superclass defines (although this is obviously
# much rarer.) If a certain platform does not have any functionality to
# add or subtract, there is no need to define it. The last platform in
# the chain that would lead up to that refinement is used instead.
#
# There are also some potentially cross-cutting behaviours: one might
# imagine, for example, that x86- and SPARC machines generally exhibit
# different behaviours but that other aspects of a platform mitigate
# those in some cases (e.g. through a compatibility API.) While it is
# certainly possible to use subclassing for this purpose, it may be
# simpler to use modules instead. Any module that .extends the base
# Platform::Attachable module will be able to represent such behaviours
# and classes may then in turn .include it when such functionality is
# desired.
#
module Platform

  # Methods for handling FFI function attachment.
  #
  # Three methods are provided for general use by this module:
  # +.attachable+ defines a wrapper to delay an +.attach_function+
  # call, +.unattachable+ undefines a previous such wrapper and
  # +.attach_foreign_functions_now+ can be used to perform the actual
  # attaching when everything is ready. See the methods for further
  # documentation.
  #
  # This module must be included, not extended.
  #
  module Attachable

    # Create a wrapper method that can be used to delay performing an
    # .attach_function. The arguments are exactly the same and they will
    # be passed through to the actual attachment unchanged. This is done
    # by creating a method named +.attach_[name]_now+, where +name+ is
    # the method name that the +.attach_function+ will use.
    #
    def self.attachable(func, name_or_args, args_or_ret, maybe_ret = nil)
      name = "attach_#{maybe_ret ? name_or_args : func}_now"
      metaclass.send(:define_method, name) { attach_function func, name_or_args, args_or_ret, maybe_ret }
    end

    # Undefine a wrapper method created by .attachable. The name given
    # is searched for as .attach_[name]_now and undefined if found. If
    # not found, a NoMethodError is raised. Undefining the method will
    # prevent it from being seen in any subclasses.
    #
    # This method is usually used to undefine a method that a parent
    # class implements on a case-by-case basis. This method should be
    # used only rarely and each time it is, it should be reviewed
    # whether this class should inherit the way it is and whether all
    # the superclasses are correctly set up. Valid use cases do exist.
    #
    def self.unattachable(name)
      metaclass.send :undef_method, "attach_#{name}_now".to_sym
    end

    # Send all the wrapper methods of the .attach_[name]_now format.
    # This will actually run the .attach_function calls so it should
    # not be used until the resolution is fully completed and the
    # platform, at least supposedly, is confirmed to support all of
    # the .attachable methods.
    #
    def self.attach_foreign_functions_now()
      methods.each {|meth| send meth if meth =~ /\Aattach_.+_now\Z/ }
    end

    # Determine whether this platform matches the one currently running on.
    #
    # This method should generally be reimplemented by all modules. Return
    # a trueish value if the module represents the current platform. Usually
    # the match is determined from RUBY_PLATFORM, but other means may also
    # certainly be used.
    #
    # A class that includes or extends an Attachable module will gain the
    # module's implementation of the method directly (see documentation
    # for +.append_features+) and should use it that way. It is possible
    # to alias and override the method in the class but it may imply that
    # either the class or the module is implemented incorrectly.
    #
    def self.currently_running?()
      raise NotImplementedError, "#{self} has not implemented .currently_running?"
    end

    # Normal +.extend+ and +.include+ semantics are bypassed: each of
    # the four module methods above become module- or class methods in
    # the receiving Module. In addition to this, any currently defined
    # +.attach_[name]_now+ methods are propagated. Finally, our versions
    # of +.append_features+ and +.extend_object+ are added to any true
    # Modules so that these semantics persist in them as well.
    #
    def self.append_features(mod)
      # Muhahahaa
      other_table = mod.metaclass.method_table
      my_table = metaclass.method_table

      names_to_move = [:attachable, :unattachable, :attach_foreign_functions_now, :currently_running?]
      names_to_move.concat [:append_features, :extend_object] if mod.class == Module

      methods.each {|m| names_to_move << m if m =~ /\Aattach_.+_now\Z/ }

      names_to_move.each {|name| other_table[name] = my_table[name].dup }
    end

    # See +.append_features+.
    metaclass.send :alias_method, :extend_object, :append_features

  end   # Attachable


  # Base is the root of the platform resolution hierarchy.
  # In addition to serving as the starting point for all
  # lookups, it also defines a few methods that are used
  # in that process. The actual FFI function attachment
  # support comes from the Attachable module.
  #
  class Base
    include Attachable

    # The defined variants of this particular platform.
    # We actually just keep an eye on +.inherited+ and
    # every subclass of this particular platform then
    # becomes a "variant" thereof.
    #
    def self.variants()
      @variants
    end

    @variants = []

    # See +.variants+. Undefines +.currently_running?+ in subclass.
    def self.inherited(variant_class)
      @variants << variant_class
      variant_class.instance_variable_set :@variants, []
      variant_class.metaclass.send :undef_method, :currently_running? rescue nil
    end

    # Checks each of the recorded variants of this platform using
    # their +.currently_running?+ methods. If there are no variants
    # or all of the ones we have return falseish, the method returns
    # self. If a variant returns trueish, though, then we call its
    # +.resolve+ and return that result.
    #
    # See +Platform.resolve+ for a higher-level overview and
    # +.currently_running?+ for more information on matching.
    #
    def self.resolve()
      # Working here currently
    end
  end


  # Resolve the class representing the current platform.
  #
  # Starting from the variants directly under Platform::Base, we
  # ask each variant whether it can match itself to the current
  # platform and, if so, recursively repeat the process with its
  # variants and so on until the currently processing variant no
  # longer has any variants of its own or none of them can match
  # the platform any further. If absolutely nothing matches, we
  # use Platform::POSIX as the default.
  #
  # When that last variant is located, it is first recorded as
  # the constant Platform::Current after which we send it the
  # message +.attach_foreign_functions_now+ to finally perform
  # the actual FFI attachment step.
  #
  # See +Attachable+, +Base.resolve+ and +Base.currently_running?+.
  #
  # NOTE: This method should never be called manually.
  #
  def self.resolve()
    platform = Base.resolve or const_get :POSIX

    platform.attach_foreign_functions_now
    const_set :Current, platform
  end

end