|
|
# 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
|