Skip to content

Commit

Permalink
watcher-rb: gradually getting there
Browse files Browse the repository at this point in the history
  • Loading branch information
Will committed Jul 21, 2024
1 parent bf1e655 commit c941f94
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 68 deletions.
2 changes: 2 additions & 0 deletions watcher-rb/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ source 'https://rubygems.org'

gemspec

gem 'ffi'
gem 'fiddle'
gem 'rake'
gem 'rspec'
gem 'solargraph'
Expand Down
5 changes: 5 additions & 0 deletions watcher-rb/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PATH
remote: .
specs:
watcher (0.11.0)
ffi

GEM
remote: https://rubygems.org/
Expand All @@ -11,6 +12,8 @@ GEM
benchmark (0.3.0)
diff-lcs (1.5.1)
e2mmap (0.1.0)
ffi (1.17.0-arm64-darwin)
fiddle (1.1.2)
jaro_winkler (1.6.0)
json (2.7.2)
kramdown (2.4.0)
Expand Down Expand Up @@ -102,6 +105,8 @@ PLATFORMS

DEPENDENCIES
bundler (~> 2.0)
ffi
fiddle
rake
rspec
solargraph
Expand Down
22 changes: 13 additions & 9 deletions watcher-rb/Rakefile
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task build_shared_library: ["lib"] do
framework_deps = case RbConfig::CONFIG["host_os"]
task build_shared_library: ['lib'] do
case RbConfig::CONFIG['host_os']
when /darwin/
"-framework CoreServices -framework CoreFoundation"
framework_deps = '-framework CoreServices -framework CoreFoundation'
link_rpath = '-Wl,-rpath,/usr/local/lib'
else
""
framework_deps = ''
link_rpath = ''
end
version = File.read("../.version").strip
version = File.read('../.version').strip
libname = "libwatcher-c-#{version}.so"
sh "c++ -shared -std=c++17 -O2 #{framework_deps} ../watcher-c/src/watcher-c.cpp -I ../watcher-c/include -o lib/#{libname}"
sources = '../watcher-c/src/watcher-c.cpp'
includes = '../watcher-c/include'
sh "c++ -shared -std=c++17 -O2 #{framework_deps} #{sources} -I #{includes} -o lib/#{libname} #{link_rpath}"
end

task default: [:clobber, :build_shared_library, :spec]
task default: %i[clobber build_shared_library spec]
121 changes: 62 additions & 59 deletions watcher-rb/lib/watcher.rb
Original file line number Diff line number Diff line change
@@ -1,57 +1,36 @@
# rubocop:disable Metrics/MethodLength
# rubocop:disable Style/Documentation
# frozen_string_literal: true

require 'fiddle'
require 'date'
require 'fiddle'
require 'fiddle/closure'
require 'fiddle/cparser'
require 'fiddle/import'
require 'fiddle/struct'

module Watcher
class CEvent < Fiddle::Struct
layout(
:path_name,
:pointer,
:effect_type,
:int8_t,
:path_type,
:int8_t,
:effect_time,
:int64_t,
:associated_path_name,
:pointer
)
end

LIB = nil

def native_solib_file_ending
case RbConfig::CONFIG['host_os']
when /darwin/
'so'
when /mswin|mingw|cygwin/
'dll'
else
'so'
end
end
C_EVENT_DATA = [
'char* path_name',
'int8_t effect_type',
'int8_t path_type',
'int64_t effect_time',
'char* associated_path_name'
].freeze

def libwatcher_c_lib_path
version = '0.11.0' # hook: tool/release
lib_name = "libwatcher-c-#{version}.#{native_solib_file_ending}"
lib_path = File.join(File.dirname(__FILE__), lib_name)
raise "Library does not exist: '#{lib_path}'" unless File.exist?(lib_path)
C_EVENT = Fiddle::Importer.struct(C_EVENT_DATA)

puts("Using library: '#{lib_path}'")
lib_path
end
C_EVENT_C_TYPE = Fiddle::Importer.parse_struct_signature(C_EVENT_DATA)

def self.lazy_static_solib_handle
return LIB if LIB
# Wraps: void (* wtr_watcher_callback)(struct wtr_watcher_event event, void* context)

@lib = Fiddle.dlopen(libwatcher_c_lib_path)
@lib.extern('void* wtr_watcher_open(char*, void*, void*)')
@lib.extern('bool wtr_watcher_close(void*)')
@lib
end
C_CALLBACK_BRIDGE_CLOSURE = Class.new(Fiddle::Closure) {
def call(c_event, rb_cb)
rb_cb.call(Watcher.c_event_to_event(c_event))
end
}.new(Fiddle::TYPE_VOID, [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP])

def to_utf8
def self.to_utf8
return '' if @value.nil?
return @value if @value.is_a?(String)
return @value.to_s.force_encoding('UTF-8') if @value.respond_to?(:to_s)
Expand Down Expand Up @@ -133,8 +112,6 @@ def to_s
end

class Event
attr_reader :path_name, :effect_type, :path_type, :effect_time, :associated_path_name

def initialize(path_name, effect_type, path_type, effect_time, associated_path_name)
@path_name = path_name
@effect_type = effect_type
Expand All @@ -155,23 +132,46 @@ def to_s

class Watch
def initialize(path, callback)
@lib = Watcher.lazy_static_solib_handle
@path = path.encode('UTF-8')
@callback = callback
@c_callback = Fiddle::Closure::BlockCaller.new(0, [CEvent, :void]) do |c_event, _|
py_event = Watcher.c_event_to_event(c_event)
@callback.call(py_event)
end

@watcher = @lib.wtr_watcher_open(@path, @c_callback, nil)
raise 'Failed to open a watcher' unless @watcher
native_solib_file_ending =
case RbConfig::CONFIG['host_os']
when /darwin/
'so'
when /mswin|mingw|cygwin/
'dll'
else
'so'
end
version = '0.11.0' # hook: tool/release
lib_name = "libwatcher-c-#{version}.#{native_solib_file_ending}"
lib_path = File.join(File.dirname(__FILE__), lib_name)
puts("Using library: '#{lib_path}'")
@_path = path.encode('UTF-8')
@_callback = callback
@_lib = Fiddle.dlopen(lib_path)
@_wtr_watcher_open = Fiddle::Function.new(
@_lib['wtr_watcher_open'],
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
Fiddle::TYPE_VOIDP
)
@_wtr_watcher_close = Fiddle::Function.new(
@_lib['wtr_watcher_close'],
[Fiddle::TYPE_VOIDP],
Fiddle::TYPE_VOIDP
)
@_c_callback_bridge = Fiddle::Function.new(
C_CALLBACK_BRIDGE_CLOSURE,
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
Fiddle::TYPE_VOID
)
@_watcher = @_wtr_watcher_open.call(@_path, @_c_callback_bridge, @_callback)
raise 'Failed to open a watcher' unless @_watcher
end

def close
return unless @watcher
return unless _watcher

@lib.wtr_watcher_close(@watcher)
@watcher = nil
_wtr_watcher_close(_watcher)
_watcher = nil
end

def finalize
Expand All @@ -190,3 +190,6 @@ def self.finalize(id)
ObjectSpace.define_finalizer(watcher, Watcher::Watch.method(:finalize).to_proc)
gets
end

# rubocop:enable Style/Documentation
# rubocop:enable Metrics/MethodLength
1 change: 1 addition & 0 deletions watcher-rb/watcher.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
spec.bindir = 'bin'
spec.executables = 'watcher'
spec.require_paths = ['lib']
spec.add_dependency 'ffi'
spec.add_development_dependency 'bundler', '~> 2.0'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.0'
Expand Down

0 comments on commit c941f94

Please sign in to comment.