# 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