Guest User

Untitled

a guest
Apr 26th, 2018
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.12 KB | None | 0 0
  1. # Our OpenBSD case would look like this:
  2.  
  3. class POSIX < Platform::PlatformTree
  4. end
  5.  
  6. class ModernUnix < POSIX
  7. attachable :lchmod, [:string, :mode_t], :int
  8. end
  9.  
  10. class BSD < ModernUnix
  11. end
  12.  
  13. class OpenBSD < BSD
  14. # No support for lchmod(). TODO: See if this can be bypassed
  15. unattachable :lchmod
  16. end
  17.  
  18. # When run, a NoMethodError gets raised when someone actually
  19. # tries to use Platform::Current.lchmod. Of course, a friendly
  20. # developer will instead use the fact that he can define the
  21. # method himself to help the user (in interest of explicitness,
  22. # the .unattachable is required):
  23.  
  24. class OpenBSD < BSD
  25. unattachable :lchmod
  26.  
  27. def self.lchmod(*)
  28. raise SomeUnrelatedException, "No lchmod on your luser OS. Sorry about your luck! Use Loonix!"
  29. end
  30. end
  31.  
  32.  
  33. ## platform.rb [ruby]
  34.  
  35. # depends on ffi.rb
  36.  
  37. class PlatformError < Exception
  38. def initialize(platform, meth, func, from, error)
  39. message = "Failed to attach foreign function `#{func}' in resolved platform #{platform}!\n" +
  40. "`#{func}' was requested to be attached by platform #{from}, as method name :#{meth}.\n" +
  41. "If the running platform (#{RUBY_PLATFORM}) does not implement this function and\n" +
  42. "you want to try to work around it, you may either make the function unattachable\n" +
  43. "on this platform (see kernel/platform/platform.rb and others) or you can set the\n" +
  44. "environment variable RBX_FFI_SOFTFAIL. In the latter case, a stub method is set\n" +
  45. "up to fail at runtime if the function is called instead of failing here.\n\n"
  46.  
  47. begin
  48. message += "The original error causing this problem was: #{error.inspect}:\n#{error.awesome_backtrace}"
  49. rescue Exception
  50. message += "Unable to produce original error message."
  51. end
  52.  
  53. super(message)
  54. end
  55. end
  56.  
  57. class DelayedPlatformError < Exception
  58. def initialize(earlier_message)
  59. super("This error has been raised because this foreign function could not be attached, and\n" +
  60. "delayed failure was requested by setting the environment variable RBX_FFI_SOFTFAIL\n" +
  61. earlier_message)
  62. end
  63. end
  64.  
  65.  
  66. # All things platform-dependent.
  67. #
  68. # The platforms Rubinius runs on can be abstracted into a rough hierarchy.
  69. # For example, all BSDs and Linux variants can be thought of as being in
  70. # the POSIX family of operating systems. The BSDs then may further break
  71. # down to the various distributions, those into their separate version
  72. # numbers and and so on. This is represented by an inheritance hierarchy
  73. # starting from Platform::PlatformTree and descending into various
  74. # subtrees. For example, the class ultimately implementing OpenBSD 4.0
  75. # would look something like OpenBSD40 < OpenBSD < BSD < POSIX < PlatformTree.
  76. #
  77. # Each platform is responsible for determining whether it matches the
  78. # one Rubinius is currently running on. This is naturally also tiered
  79. # so that, for example, OpenBSD could just check for "open" since BSD
  80. # already checked for "bsd" (to simplify a bit.)
  81. #
  82. # Each step along the way can either define additional behaviour or then
  83. # remove behaviour that a superclass defines (although this is obviously
  84. # much rarer.) If a certain platform does not have any functionality to
  85. # add or subtract, there is no need to define it. The last platform in
  86. # the chain that would lead up to that refinement is used instead.
  87. #
  88. # There are also some potentially cross-cutting behaviours: one might
  89. # imagine, for example, that x86- and SPARC machines generally exhibit
  90. # different behaviours but that other aspects of a platform mitigate
  91. # those in some cases (e.g. through a compatibility API.) While it is
  92. # certainly possible to use subclassing for this purpose, it may be
  93. # simpler to use modules instead. The module must +extend+ the utility
  94. # module Platform::SharedSharedBehaviour and can thereafter define
  95. # its own attachable and unattachables. A platform class will then
  96. # +include+ it to gain access to that information.
  97. #
  98. module Platform
  99.  
  100. # Methods for handling FFI function attachment.
  101. #
  102. # NOTE: .extend this only.
  103. #
  104. module Attachable
  105.  
  106. # The functions pending to be attached to this platform or
  107. # its variants. The data is kept in an Array, and each entry
  108. # is further composed of two Arrays. The first element has
  109. # the name that the method will take after being attached
  110. # and a reference to the class in which it was market to be
  111. # attachable. The second element is just the exact set of
  112. # arguments that .attachable was called with, to be passed
  113. # to .attach_function.
  114. #
  115. def attachables=(a); @attachables = a; end
  116. def attachables(); @attachables; end
  117.  
  118. # Record a function to attach later.
  119. #
  120. # Stores the information about the function so that it can be referred
  121. # to when everything is set up for the actual attaching to be done. We
  122. # also store a reference to +self+ wherever the attachment happens,
  123. # mainly for diagnostic purposes. If the attachables array did not yet
  124. # exist (i.e., this is the first function), it is created.
  125. #
  126. # If the method name to be already exists in attachables, a NameError
  127. # is raised. It should be removed with #unattachable first.
  128. #
  129. # The arguments are exactly the same as for Module#attach_function and
  130. # will eventually be used as such.
  131. #
  132. def attachable(func, name_or_args, args_or_ret, maybe_ret = nil)
  133. name = maybe_ret ? name_or_args : func
  134.  
  135. if @attachables
  136. i = index_for name
  137.  
  138. if i
  139. existing = @attachables[i]
  140. raise NameError, "Attachable :#{name} already exists! You must remove it with #unattachable first.\n" +
  141. "Existing :#{name} defined for function named :#{existing[1][0]} in #{existing[0][1]}."
  142. end
  143. else
  144. @attachables = []
  145. end
  146.  
  147. @attachables[@attachables.size] = [[name, self], [func, name_or_args, args_or_ret, maybe_ret]]
  148. end
  149.  
  150. # Remove the entry for a function previously marked attachable. If the
  151. # given name does not exist in attachables, a NameError is raised. The
  152. # entry is not _deleted_, it is simply set to nil.
  153. #
  154. def unattachable(name)
  155. i = index_for name
  156. raise NameError, "No attachable named #{name} in #{self.name}" unless i
  157. @attachables[i] = nil
  158. end
  159.  
  160. # Index of a method name in attachables or nil.
  161. #
  162. def index_for(name)
  163. i = 0
  164. while @attachables and i < @attachables.size
  165. return i if @attachables[i] and @attachables[i][0][0].to_sym == name.to_sym
  166. i += 1
  167. end
  168. end
  169.  
  170. # Add our functionality to +other+ as singletons.
  171. #
  172. def self.extend_object(other)
  173. IncludedModule.new(self).attach_to other.metaclass
  174. end
  175.  
  176. # No +include+ing allowed.
  177. #
  178. def self.append_features(other)
  179. raise TypeError, "#{self} must be #extended"
  180. end
  181. end
  182.  
  183. # Shared or cross-cutting behaviour.
  184. #
  185. # Any platform-dependencies that may be shared between otherwise
  186. # disparate platforms (e.g. from different trees) can be given
  187. # in a module that has #extended this module. That module may be
  188. # then further extended by any of the platform classes.
  189. #
  190. # Any module that wishes to be used for such a purpose must
  191. # extend this module.
  192. #
  193. # We include Attachable so that the same mechanism can be used
  194. # to define functions etc. Unattachables are a bit different in
  195. # that they do not affect this module at all: they will all be
  196. # run when a platform +include+s us. This allows easy stripping
  197. # of inherited behaviour in platforms.
  198. #
  199. module SharedBehaviour
  200. extend Attachable
  201.  
  202. # Record for method names to run unattachable on in the
  203. # receiving platform when being extended into it. The
  204. # name of the method and the platform class or module
  205. # that requested it are recorded.
  206. def unattachables=(u); @unattachables = u; end
  207. def unattachables(); @unattachables ||= {}; end
  208.  
  209. # Record a method name to be made unattachable when extending.
  210. #
  211. # Both unattachable methods and attachable methods defined in this
  212. # module are gathered and run only when being extended into another
  213. # module or a platform class. The unattachables are stored in a
  214. # Hash so they cannot be removed twice, since order of definition
  215. # does not matter here.
  216. #
  217. def unattachable(name)
  218. unattachables[name.to_sym] = name
  219. end
  220.  
  221. # Extending shared behaviour modules is currently disallowed.
  222. #
  223. # TODO: Maybe allow composing with other modules.
  224. #
  225. def extend_object(other)
  226. raise TypeError, "#{self} must not be further extended. Please extend SharedBehaviour directly"
  227. end
  228.  
  229. # Apply this behaviour to a platform class.
  230. #
  231. # The recipient _must_ be an PlatformTree subclass
  232. #
  233. def append_features(other)
  234. raise TypeError, "#{self} only includable in platform classes" unless other.class == Class
  235.  
  236. unattachables.each {|name, _| other.unattachable name }
  237.  
  238. if attachables
  239. others = other.attachables
  240. i, to_add = 0, @attachables.size
  241.  
  242. while i < to_add
  243. mine = @attachables[i]
  244.  
  245. if other.index_for mine[0][0]
  246. raise NameError, "Attachable #{mine[0][0]} already exists in #{other}! You must remove it with #unattachable first.\n" +
  247. "The existing #{name} was defined for function named #{existing[1][0]} by #{existing[0][1]}."
  248. end
  249.  
  250. others[others.size] = mine.dup
  251. i += 1
  252. end
  253. end
  254. end
  255.  
  256. # Add this functionality to another Module.
  257. #
  258. # Add instance methods from Attachable and this module as singleton
  259. # methods to the recipient which must also be a true Module. Callback
  260. # is not triggered.
  261. #
  262. def self.extend_object(other)
  263. raise TypeError, "#{self} may only be extended by true Modules" unless other.class == Module
  264.  
  265. other = other.metaclass
  266.  
  267. IncludedModule.new(Platform::Attachable).attach_to other
  268. IncludedModule.new(self).attach_to other
  269. end
  270.  
  271. # No +include+ing allowed.
  272. #
  273. def self.append_features(other)
  274. raise TypeError, "#{self} must be #extended and only by a true Module"
  275. end
  276.  
  277. end
  278.  
  279. # PlatformTree is the root of the platform resolution hierarchy.
  280. # In addition to serving as the starting point for all lookups, it
  281. # also defines a few methods that are used in that process. Some of
  282. # the methods from Attachable will be overridden here.
  283. #
  284. class PlatformTree
  285. extend Attachable
  286. @attachables = []
  287.  
  288. # The defined variants of this particular platform.
  289. # We actually just keep an eye on +.inherited+ and
  290. # every subclass of this particular platform then
  291. # becomes a "variant" thereof.
  292. #
  293. def self.variants=(v); @variants = v; end
  294. def self.variants(); @variants; end
  295. @variants = []
  296.  
  297. # Each variant subclassing this platform requires a little
  298. # bit of set-up. First, +.currently_running?+ is undefined
  299. # to avoid accidentally overlooking implementing it. We also
  300. # create the initial .variants and .attachables data for the
  301. # subclass. The latter is done by copying over our .attachables.
  302. #
  303. def self.inherited(variant_class)
  304. @variants << variant_class
  305. variant_class.variants = []
  306. variant_class.attachables = @attachables.dup
  307. variant_class.metaclass.send :undef_method, :currently_running? rescue nil
  308. end
  309.  
  310. # Determine whether this platform matches the one currently running on.
  311. #
  312. # This method should generally be reimplemented by all variants. Return
  313. # a trueish value if the module represents the current platform. Usually
  314. # the match is determined from RUBY_PLATFORM, but other means may also
  315. # certainly be used.
  316. #
  317. # If the variant includes a SharedBehaviour that defines this method,
  318. # generally it should not be overridden.
  319. #
  320. def self.currently_running?()
  321. raise NotImplementedError, "#{self} has not implemented .currently_running?"
  322. end
  323.  
  324. # Checks each of the recorded variants of this platform using
  325. # their +.currently_running?+ methods. If there are no variants
  326. # or all of the ones we have return falseish, the method returns
  327. # self. If a variant returns trueish, though, then we call its
  328. # +.resolve+ and return that result.
  329. #
  330. # See +Platform.resolve+ for a higher-level overview and
  331. # +.currently_running?+ for more information on matching.
  332. #
  333. def self.resolve()
  334. @variants.each do |variant|
  335. return variant.resolve if variant.currently_running?
  336. end
  337.  
  338. self
  339. end
  340.  
  341. # Actually attach all functions.
  342. #
  343. # Each attachable currently defined for this class is
  344. # sent to .attach_function. If the function is located,
  345. # all is fine and the method is added normally. If it
  346. # is not located (or another error occurs in the FFI
  347. # layer), an error is raised with a bunch of detail
  348. # about the problem and resolution steps.
  349. #
  350. # If the environment variable RBX_FFI_SOFTFAIL is set,
  351. # any errors from .attach_function are caught silently
  352. # instead. We also define a stub method in place of the
  353. # absent real thing. When actually called, the stub will
  354. # just raise the same error information that would have
  355. # been shown at attach-time along with a note about the
  356. # env.
  357. #
  358. def self.attach_functions_now()
  359. i, count = 0, @attachables.size
  360.  
  361. while i < count
  362. # We may have nils from unattachments
  363. attach_function *@attachables[i][1] if @attachables[i]
  364. i += 1
  365. end
  366.  
  367. rescue Exception => e
  368. meth, from = @attachables[i][0]
  369. func = @attachables[i][1][0]
  370. error = PlatformError.new self, meth, func, from, e
  371.  
  372. # User may want to delay raising until function actually needed
  373. raise error unless ENV["RBX_FFI_SOFTFAIL"]
  374.  
  375. instance_eval %{
  376. def self.#{meth}(*)
  377. message = #{error.message.dump}
  378. raise DelayedPlatformError, message
  379. end
  380. }
  381. end
  382. end
  383.  
  384.  
  385. # Resolve the class representing the current platform.
  386. #
  387. # Starting from the variants directly under Platform::PlatformTree, we
  388. # ask each variant whether it can match itself to the current platform
  389. # and, if so, recursively repeat the process with its variants and so
  390. # on until the currently processing variant no longer has any variants
  391. # of its own or none of them can match the platform any further. If
  392. # absolutely nothing matches, we use Platform::POSIX as the default.
  393. #
  394. # When that last variant is located, it is first recorded as the
  395. # constant Platform::Current after which we send it the message
  396. # +.attach_functions_now+ to finally perform the actual FFI
  397. # attachment step.
  398. #
  399. # NOTE: This method should never be called manually.
  400. #
  401. def self.resolve()
  402. platform = PlatformTree.resolve
  403. platform = const_get :POSIX if platform == PlatformTree
  404.  
  405. platform.attach_functions_now
  406. const_set :Current, platform
  407. end
  408.  
  409. end
Add Comment
Please, Sign In to add comment