Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Our OpenBSD case would look like this:
- class POSIX < Platform::PlatformTree
- end
- class ModernUnix < POSIX
- attachable :lchmod, [:string, :mode_t], :int
- end
- class BSD < ModernUnix
- end
- class OpenBSD < BSD
- # No support for lchmod(). TODO: See if this can be bypassed
- unattachable :lchmod
- end
- # When run, a NoMethodError gets raised when someone actually
- # tries to use Platform::Current.lchmod. Of course, a friendly
- # developer will instead use the fact that he can define the
- # method himself to help the user (in interest of explicitness,
- # the .unattachable is required):
- class OpenBSD < BSD
- unattachable :lchmod
- def self.lchmod(*)
- raise SomeUnrelatedException, "No lchmod on your luser OS. Sorry about your luck! Use Loonix!"
- end
- end
- ## platform.rb [ruby]
- # depends on ffi.rb
- class PlatformError < Exception
- def initialize(platform, meth, func, from, error)
- message = "Failed to attach foreign function `#{func}' in resolved platform #{platform}!\n" +
- "`#{func}' was requested to be attached by platform #{from}, as method name :#{meth}.\n" +
- "If the running platform (#{RUBY_PLATFORM}) does not implement this function and\n" +
- "you want to try to work around it, you may either make the function unattachable\n" +
- "on this platform (see kernel/platform/platform.rb and others) or you can set the\n" +
- "environment variable RBX_FFI_SOFTFAIL. In the latter case, a stub method is set\n" +
- "up to fail at runtime if the function is called instead of failing here.\n\n"
- begin
- message += "The original error causing this problem was: #{error.inspect}:\n#{error.awesome_backtrace}"
- rescue Exception
- message += "Unable to produce original error message."
- end
- super(message)
- end
- end
- class DelayedPlatformError < Exception
- def initialize(earlier_message)
- super("This error has been raised because this foreign function could not be attached, and\n" +
- "delayed failure was requested by setting the environment variable RBX_FFI_SOFTFAIL\n" +
- earlier_message)
- end
- end
- # 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 Platform::PlatformTree and descending into various
- # subtrees. For example, the class ultimately implementing OpenBSD 4.0
- # would look something like OpenBSD40 < OpenBSD < BSD < POSIX < PlatformTree.
- #
- # Each platform is responsible for determining whether it matches the
- # one Rubinius is currently running on. This is naturally also tiered
- # so that, for example, OpenBSD could just check for "open" since BSD
- # already checked for "bsd" (to simplify a bit.)
- #
- # 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. The module must +extend+ the utility
- # module Platform::SharedSharedBehaviour and can thereafter define
- # its own attachable and unattachables. A platform class will then
- # +include+ it to gain access to that information.
- #
- module Platform
- # Methods for handling FFI function attachment.
- #
- # NOTE: .extend this only.
- #
- module Attachable
- # The functions pending to be attached to this platform or
- # its variants. The data is kept in an Array, and each entry
- # is further composed of two Arrays. The first element has
- # the name that the method will take after being attached
- # and a reference to the class in which it was market to be
- # attachable. The second element is just the exact set of
- # arguments that .attachable was called with, to be passed
- # to .attach_function.
- #
- def attachables=(a); @attachables = a; end
- def attachables(); @attachables; end
- # Record a function to attach later.
- #
- # Stores the information about the function so that it can be referred
- # to when everything is set up for the actual attaching to be done. We
- # also store a reference to +self+ wherever the attachment happens,
- # mainly for diagnostic purposes. If the attachables array did not yet
- # exist (i.e., this is the first function), it is created.
- #
- # If the method name to be already exists in attachables, a NameError
- # is raised. It should be removed with #unattachable first.
- #
- # The arguments are exactly the same as for Module#attach_function and
- # will eventually be used as such.
- #
- def attachable(func, name_or_args, args_or_ret, maybe_ret = nil)
- name = maybe_ret ? name_or_args : func
- if @attachables
- i = index_for name
- if i
- existing = @attachables[i]
- raise NameError, "Attachable :#{name} already exists! You must remove it with #unattachable first.\n" +
- "Existing :#{name} defined for function named :#{existing[1][0]} in #{existing[0][1]}."
- end
- else
- @attachables = []
- end
- @attachables[@attachables.size] = [[name, self], [func, name_or_args, args_or_ret, maybe_ret]]
- end
- # Remove the entry for a function previously marked attachable. If the
- # given name does not exist in attachables, a NameError is raised. The
- # entry is not _deleted_, it is simply set to nil.
- #
- def unattachable(name)
- i = index_for name
- raise NameError, "No attachable named #{name} in #{self.name}" unless i
- @attachables[i] = nil
- end
- # Index of a method name in attachables or nil.
- #
- def index_for(name)
- i = 0
- while @attachables and i < @attachables.size
- return i if @attachables[i] and @attachables[i][0][0].to_sym == name.to_sym
- i += 1
- end
- end
- # Add our functionality to +other+ as singletons.
- #
- def self.extend_object(other)
- IncludedModule.new(self).attach_to other.metaclass
- end
- # No +include+ing allowed.
- #
- def self.append_features(other)
- raise TypeError, "#{self} must be #extended"
- end
- end
- # Shared or cross-cutting behaviour.
- #
- # Any platform-dependencies that may be shared between otherwise
- # disparate platforms (e.g. from different trees) can be given
- # in a module that has #extended this module. That module may be
- # then further extended by any of the platform classes.
- #
- # Any module that wishes to be used for such a purpose must
- # extend this module.
- #
- # We include Attachable so that the same mechanism can be used
- # to define functions etc. Unattachables are a bit different in
- # that they do not affect this module at all: they will all be
- # run when a platform +include+s us. This allows easy stripping
- # of inherited behaviour in platforms.
- #
- module SharedBehaviour
- extend Attachable
- # Record for method names to run unattachable on in the
- # receiving platform when being extended into it. The
- # name of the method and the platform class or module
- # that requested it are recorded.
- def unattachables=(u); @unattachables = u; end
- def unattachables(); @unattachables ||= {}; end
- # Record a method name to be made unattachable when extending.
- #
- # Both unattachable methods and attachable methods defined in this
- # module are gathered and run only when being extended into another
- # module or a platform class. The unattachables are stored in a
- # Hash so they cannot be removed twice, since order of definition
- # does not matter here.
- #
- def unattachable(name)
- unattachables[name.to_sym] = name
- end
- # Extending shared behaviour modules is currently disallowed.
- #
- # TODO: Maybe allow composing with other modules.
- #
- def extend_object(other)
- raise TypeError, "#{self} must not be further extended. Please extend SharedBehaviour directly"
- end
- # Apply this behaviour to a platform class.
- #
- # The recipient _must_ be an PlatformTree subclass
- #
- def append_features(other)
- raise TypeError, "#{self} only includable in platform classes" unless other.class == Class
- unattachables.each {|name, _| other.unattachable name }
- if attachables
- others = other.attachables
- i, to_add = 0, @attachables.size
- while i < to_add
- mine = @attachables[i]
- if other.index_for mine[0][0]
- raise NameError, "Attachable #{mine[0][0]} already exists in #{other}! You must remove it with #unattachable first.\n" +
- "The existing #{name} was defined for function named #{existing[1][0]} by #{existing[0][1]}."
- end
- others[others.size] = mine.dup
- i += 1
- end
- end
- end
- # Add this functionality to another Module.
- #
- # Add instance methods from Attachable and this module as singleton
- # methods to the recipient which must also be a true Module. Callback
- # is not triggered.
- #
- def self.extend_object(other)
- raise TypeError, "#{self} may only be extended by true Modules" unless other.class == Module
- other = other.metaclass
- IncludedModule.new(Platform::Attachable).attach_to other
- IncludedModule.new(self).attach_to other
- end
- # No +include+ing allowed.
- #
- def self.append_features(other)
- raise TypeError, "#{self} must be #extended and only by a true Module"
- end
- end
- # PlatformTree 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. Some of
- # the methods from Attachable will be overridden here.
- #
- class PlatformTree
- extend Attachable
- @attachables = []
- # 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=(v); @variants = v; end
- def self.variants(); @variants; end
- @variants = []
- # Each variant subclassing this platform requires a little
- # bit of set-up. First, +.currently_running?+ is undefined
- # to avoid accidentally overlooking implementing it. We also
- # create the initial .variants and .attachables data for the
- # subclass. The latter is done by copying over our .attachables.
- #
- def self.inherited(variant_class)
- @variants << variant_class
- variant_class.variants = []
- variant_class.attachables = @attachables.dup
- variant_class.metaclass.send :undef_method, :currently_running? rescue nil
- end
- # Determine whether this platform matches the one currently running on.
- #
- # This method should generally be reimplemented by all variants. 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.
- #
- # If the variant includes a SharedBehaviour that defines this method,
- # generally it should not be overridden.
- #
- def self.currently_running?()
- raise NotImplementedError, "#{self} has not implemented .currently_running?"
- 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()
- @variants.each do |variant|
- return variant.resolve if variant.currently_running?
- end
- self
- end
- # Actually attach all functions.
- #
- # Each attachable currently defined for this class is
- # sent to .attach_function. If the function is located,
- # all is fine and the method is added normally. If it
- # is not located (or another error occurs in the FFI
- # layer), an error is raised with a bunch of detail
- # about the problem and resolution steps.
- #
- # If the environment variable RBX_FFI_SOFTFAIL is set,
- # any errors from .attach_function are caught silently
- # instead. We also define a stub method in place of the
- # absent real thing. When actually called, the stub will
- # just raise the same error information that would have
- # been shown at attach-time along with a note about the
- # env.
- #
- def self.attach_functions_now()
- i, count = 0, @attachables.size
- while i < count
- # We may have nils from unattachments
- attach_function *@attachables[i][1] if @attachables[i]
- i += 1
- end
- rescue Exception => e
- meth, from = @attachables[i][0]
- func = @attachables[i][1][0]
- error = PlatformError.new self, meth, func, from, e
- # User may want to delay raising until function actually needed
- raise error unless ENV["RBX_FFI_SOFTFAIL"]
- instance_eval %{
- def self.#{meth}(*)
- message = #{error.message.dump}
- raise DelayedPlatformError, message
- end
- }
- end
- end
- # Resolve the class representing the current platform.
- #
- # Starting from the variants directly under Platform::PlatformTree, 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_functions_now+ to finally perform the actual FFI
- # attachment step.
- #
- # NOTE: This method should never be called manually.
- #
- def self.resolve()
- platform = PlatformTree.resolve
- platform = const_get :POSIX if platform == PlatformTree
- platform.attach_functions_now
- const_set :Current, platform
- end
- end
Add Comment
Please, Sign In to add comment