ruble 0.0.3.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gdbinit +21 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +96 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +6 -0
  7. data/CMakeLists.txt +4 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/Gemfile +21 -0
  10. data/Gemfile.lock +98 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +63 -0
  13. data/Rakefile +41 -0
  14. data/ext/ruble/.gitignore +5 -0
  15. data/ext/ruble/CMakeLists.txt +157 -0
  16. data/ext/ruble/RuBLEHelpers.cmake +240 -0
  17. data/ext/ruble/bindings/Adapter.cpp +193 -0
  18. data/ext/ruble/bindings/Adapter.hpp +85 -0
  19. data/ext/ruble/bindings/Characteristic.cpp +171 -0
  20. data/ext/ruble/bindings/Characteristic.hpp +132 -0
  21. data/ext/ruble/bindings/Descriptor.cpp +34 -0
  22. data/ext/ruble/bindings/Descriptor.hpp +69 -0
  23. data/ext/ruble/bindings/Peripheral.cpp +212 -0
  24. data/ext/ruble/bindings/Peripheral.hpp +108 -0
  25. data/ext/ruble/bindings/RuBLE.cpp +115 -0
  26. data/ext/ruble/bindings/Service.cpp +112 -0
  27. data/ext/ruble/bindings/Service.hpp +61 -0
  28. data/ext/ruble/bindings/common.hpp +48 -0
  29. data/ext/ruble/bindings/globals.cpp +43 -0
  30. data/ext/ruble/cmake.mk +62 -0
  31. data/ext/ruble/concerns/CharacteristicValueTracker.cpp +18 -0
  32. data/ext/ruble/concerns/CharacteristicValueTracker.hpp +40 -0
  33. data/ext/ruble/concerns/Rubyable.cpp +4 -0
  34. data/ext/ruble/concerns/Rubyable.hpp +46 -0
  35. data/ext/ruble/config.h.in +25 -0
  36. data/ext/ruble/containers/ByteArray.cpp +64 -0
  37. data/ext/ruble/containers/ByteArray.hpp +161 -0
  38. data/ext/ruble/containers/Callback.hpp +52 -0
  39. data/ext/ruble/containers/NamedBitSet.hpp +140 -0
  40. data/ext/ruble/containers/NamedBitSet.ipp +71 -0
  41. data/ext/ruble/extconf.rb +30 -0
  42. data/ext/ruble/management/Registry.cpp +63 -0
  43. data/ext/ruble/management/Registry.hpp +170 -0
  44. data/ext/ruble/management/RegistryFactory.hpp +113 -0
  45. data/ext/ruble/management/RubyQueue.cpp +152 -0
  46. data/ext/ruble/management/RubyQueue.hpp +69 -0
  47. data/ext/ruble/modularize.diff +28 -0
  48. data/ext/ruble/types/SimpleBLE.hpp +21 -0
  49. data/ext/ruble/types/declarations.hpp +91 -0
  50. data/ext/ruble/types/helpers.hpp +12 -0
  51. data/ext/ruble/types/ruby.hpp +36 -0
  52. data/ext/ruble/types/stl.hpp +41 -0
  53. data/ext/ruble/utils/RubyCallbackTraits.cpp +28 -0
  54. data/ext/ruble/utils/RubyCallbackTraits.hpp +48 -0
  55. data/ext/ruble/utils/async.cpp +10 -0
  56. data/ext/ruble/utils/async.hpp +76 -0
  57. data/ext/ruble/utils/containers.hpp +41 -0
  58. data/ext/ruble/utils/exception_handling.cpp +50 -0
  59. data/ext/ruble/utils/exception_handling.hpp +53 -0
  60. data/ext/ruble/utils/garbage_collection.cpp +82 -0
  61. data/ext/ruble/utils/garbage_collection.hpp +22 -0
  62. data/ext/ruble/utils/hash.cpp +83 -0
  63. data/ext/ruble/utils/hash.hpp +52 -0
  64. data/ext/ruble/utils/hexadecimal.hpp +116 -0
  65. data/ext/ruble/utils/human_type_names.hpp +38 -0
  66. data/ext/ruble/utils/inspection.cpp +24 -0
  67. data/ext/ruble/utils/inspection.hpp +108 -0
  68. data/ext/ruble/utils/ruby.hpp +103 -0
  69. data/ext/ruble/utils/ruby_context.hpp +73 -0
  70. data/lib/ruble/build/.rubocop.yml +19 -0
  71. data/lib/ruble/build/boost.rb +34 -0
  72. data/lib/ruble/build/cmake.rb +134 -0
  73. data/lib/ruble/build/core_ext.rb +5 -0
  74. data/lib/ruble/build/data/bundler.rb +24 -0
  75. data/lib/ruble/build/data/extension.rb +101 -0
  76. data/lib/ruble/build/data/os.rb +21 -0
  77. data/lib/ruble/build/data/rice.rb +24 -0
  78. data/lib/ruble/build/data.rb +22 -0
  79. data/lib/ruble/build/extconf.rb +76 -0
  80. data/lib/ruble/build/github_repo.rb +129 -0
  81. data/lib/ruble/build/simpleble.rb +56 -0
  82. data/lib/ruble/build.rb +28 -0
  83. data/lib/ruble/version.rb +7 -0
  84. data/lib/ruble.rb +46 -0
  85. data/lib/tasks/dev/dev_tasks.rb +130 -0
  86. data/lib/tasks/dev/pager.rb +218 -0
  87. data/lib/tasks/dev/paths.rb +30 -0
  88. data/lib/tasks/dev/state_hash.rb +65 -0
  89. data/lib/tasks/dev.rake +41 -0
  90. data/lib/tasks/simpleble.rake +29 -0
  91. data/sig/rubble.rbs +4 -0
  92. metadata +263 -0
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module RuBLE
6
+ module Build
7
+ module Data
8
+ module Extension
9
+ include Memery
10
+
11
+ memoize def gem_spec = Data.this_gem
12
+ memoize def gem_name = gem_spec.name.freeze
13
+ memoize def root_dir = Data.bundler.root.cleanpath(false)
14
+ # memoize def gem_full_path = Pathname.new(gem_spec.full_gem_path).freeze
15
+ def gem_full_path = root_dir
16
+
17
+ memoize def gem_version = gem_spec.version.freeze
18
+ def extension_version = gem_version
19
+
20
+ memoize def extension_names
21
+ gem_spec.extensions.map do |extconf_path|
22
+ Pathname.new(extconf_path).parent.basename.to_s.freeze
23
+ end.freeze
24
+ end
25
+ memoize def extension_name = extension_names.first
26
+
27
+ memoize def git_root = run_cmd('git rev-parse --show-toplevel', path: true)
28
+ memoize def git_root? = !!git_root
29
+ memoize def git_head = run_cmd('git rev-parse HEAD', chdir: git_root)
30
+ memoize def git_status = run_cmd('git status --porcelain', chdir: git_root)
31
+ memoize def git_dirty? = git_status.nil? || git_status&.empty?
32
+ memoize def git_last_tag = run_cmd('git describe --tags --abbrev=0', chdir: git_root)
33
+ memoize def git_tagged? = git_root? && !git_dirty? && git_last_tag == git_head
34
+ memoize def git_tag = (git_last_tag if git_tagged?)
35
+ memoize def git_commit_str = [git_head, git_dirty? && '-dirty'].select(&:itself).join
36
+
37
+ memoize def github_url = gem_spec.metadata['source_code_uri']
38
+ memoize def github_release_asset(asset:, tag: git_tag)
39
+ "#{github_url}/releases/download/#{tag}/#{asset}".freeze if tag
40
+ end
41
+
42
+ memoize def debug_asset_name = "#{extension_name}.#{target_os}-#{target_cpu}.so.debug"
43
+ memoize def debug_info_url = github_release_asset(asset: debug_asset_name)
44
+
45
+ memoize def target_os = RbConfig::CONFIG['target_os']
46
+ memoize def target_cpu = RbConfig::CONFIG['target_cpu']
47
+
48
+ # library metadata (such as version/commit hash, etc) to be passed to
49
+ # `ld --package-metadata` (https://systemd.io/ELF_PACKAGE_METADATA/)
50
+ memoize def linker_package_metadata
51
+ {
52
+ type: 'gem',
53
+ name: gem_name,
54
+ version: gem_version,
55
+ dirty: git_dirty?,
56
+ commit: git_commit_str,
57
+ release: git_tag,
58
+ os: target_os,
59
+ sysname: Etc.uname.fetch(:sysname),
60
+ os_rel: Etc.uname.fetch(:release),
61
+ os_ver: Etc.uname.fetch(:version),
62
+ machine: Etc.uname.fetch(:machine),
63
+ arch: target_cpu,
64
+ build_os: RbConfig::CONFIG['build_os'],
65
+ arch_os: RbConfig::CONFIG['build_cpu'],
66
+ libc: (Etc.confstr(Etc::CS_GNU_LIBC_VERSION) rescue nil),
67
+ pthread: (Etc.confstr(Etc::CS_GNU_LIBPTHREAD_VERSION) rescue nil),
68
+ debugInfoUrl: debug_info_url,
69
+ }.compact.transform_keys(&:to_s).freeze
70
+ end
71
+
72
+ memoize def extension_data
73
+ {
74
+ gem_name: gem_name,
75
+ name: extension_name,
76
+ version: gem_version.to_s,
77
+ path: gem_full_path.to_s,
78
+ package: {
79
+ linker_metadata: linker_package_metadata.to_json,
80
+ },
81
+ debug_build: true,
82
+ }.freeze
83
+ end
84
+
85
+ private
86
+
87
+ def run_cmd(*args, chdir: __dir__, path: false, **kwargs)
88
+ return nil if chdir.nil?
89
+
90
+ out, status = ::Open3.capture2(*args, chdir:, err: :close, stdin_data: '', **kwargs)
91
+ return nil unless status.success?
92
+
93
+ out.strip!
94
+ return out.empty? ? nil : Pathname.new(out).cleanpath(false) if path
95
+
96
+ out
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ module Data
6
+ # noinspection RubyClassModuleNamingConvention
7
+ module OS
8
+ include Memery
9
+
10
+ OS_FIELDS = %i[dev_null cpu_count host posix? linux? host_os iron_ruby? bits host_cpu
11
+ java? windows? mac? jruby? osx? x? freebsd? cygwin?].freeze
12
+
13
+ memoize def os_data
14
+ result = OS_FIELDS.to_h { |name| [name.to_s.delete_suffix('?'), ::OS.send(name)] }
15
+ result['os_release'] = OS.parse_os_release rescue nil
16
+ result.compact.freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ module Data
6
+ module Rice
7
+ include Memery
8
+
9
+ memoize def rice_spec = Data['rice']
10
+ memoize def rice_full_path = Pathname.new(rice_spec.full_gem_path).freeze
11
+ memoize def rice_include_dir = (rice_full_path / 'include').freeze
12
+ memoize def rice_version = rice_spec.version.freeze
13
+
14
+ memoize def rice_data
15
+ {
16
+ gem_path: rice_full_path.to_s,
17
+ include_dir: rice_include_dir.to_s,
18
+ version: rice_version.to_s,
19
+ }.freeze
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ module Data
6
+ class << self
7
+ include Memery
8
+
9
+ memoize def bundler = ::Bundler.load
10
+ memoize def specs = bundler.specs
11
+
12
+ memoize def this_gem
13
+ specs.find { |gem| gem.full_gem_path == bundler.root.to_s }
14
+ end
15
+
16
+ memoize def [](name)
17
+ specs.find_by_name_and_platform(name, RbConfig::CONFIG['target'])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ class Extconf
6
+ include Memery
7
+
8
+ class << self
9
+ private :new
10
+ def instance = @instance ||= new
11
+
12
+ def add_github_repo_data(name)
13
+ klass_name = RuBLE::Build.zeitwerk.inflector.camelize(name.to_s, __dir__).to_sym
14
+ klass = RuBLE::Build.const_get(klass_name)
15
+ define_method name.to_sym do
16
+ # TODO: accept custom path
17
+ # TODO: choose shared vs static
18
+ # TODO: choose build vs precompiled
19
+
20
+ static = enable_config("#{name}-static", true)
21
+ precompiled = enable_config("precompiled-#{name}", true)
22
+
23
+ tag = with_config("#{name}-release")&.freeze || 'default'
24
+ klass.new(tag:, static:, precompiled:)
25
+ end
26
+ memoize name.to_sym
27
+ klass
28
+ end
29
+ end
30
+
31
+ def initialize
32
+ ENV[debug_env_var] = 'on' if debug_mode?
33
+ end
34
+
35
+ include RuBLE::Build::Data::OS
36
+ include RuBLE::Build::Data::Bundler
37
+ include RuBLE::Build::Data::Rice
38
+ include RuBLE::Build::Data::Extension
39
+ include RuBLE::Build::CMake
40
+
41
+ memoize def debug_env_var = "#{gem_name.upcase}_DEBUG_ON"
42
+
43
+ memoize def debug_mode? = with_config('debug') || ENV[debug_env_var] # rubocop:disable Style/FetchEnvVar
44
+
45
+ memoize def default_library_version(name:)
46
+ gem_spec.metadata.fetch("#{name}_library_version")
47
+ end
48
+
49
+ memoize def rbconfig_data
50
+ RbConfig::CONFIG.select do |_k, v|
51
+ v && !v.strip.empty?
52
+ end
53
+ end
54
+
55
+ add_github_repo_data :simpleble
56
+ add_github_repo_data :boost
57
+
58
+ memoize def config_data
59
+ @config_data = {
60
+ bundler: bundler_data,
61
+ extconf: extension_data,
62
+ rb: rbconfig_data,
63
+ rb_os: os_data,
64
+ rb_simpleble: simpleble.config_data,
65
+ rb_boost: boost.config_data,
66
+ rice: rice_data,
67
+ }.each_value(&:freeze).freeze
68
+ end
69
+
70
+ def write_build_config(path:)
71
+ path.unlink if path.file?
72
+ cmake_generate(config_data, path:)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ class GithubRepo
6
+ include Memery
7
+ include Data::Extension
8
+
9
+ GITHUB_API_VERSION = '2022-11-28'
10
+
11
+ class << self
12
+ def github_repo = const_get(:GITHUB_REPO)
13
+ def github_repo_url = "https://github.com/#{github_repo}"
14
+ def github_api_base_url = "https://api.github.com/repos/#{github_repo}"
15
+ end
16
+
17
+ attr_reader *%i[requested_tag static precompiled]
18
+ def initialize(tag: 'default', static: true, precompiled: true)
19
+ # rubocop:disable Style/ClassEqualityComparison
20
+ raise "Cannot instantiate #{self.class} directly. Must subclass." if self.class == GithubRepo
21
+
22
+ @requested_tag = tag.to_s.freeze
23
+ @static = static
24
+ @precompiled = precompiled
25
+ end
26
+
27
+ memoize def gem_spec = Extconf.instance.gem_spec
28
+ memoize def github_repo = self.class.github_repo
29
+ memoize def github_repo_url = self.class.github_repo_url
30
+ memoize def github_api_base_url = self.class.github_api_base_url
31
+
32
+ memoize def static? = static
33
+ memoize def linkage = (static? ? 'static' : 'dynamic')
34
+ memoize def precompiled? = precompiled
35
+ memoize def build? = !precompiled?
36
+
37
+ memoize def latest_release(include_prereleases: false)
38
+ github_api.get('releases').body.detect do |release|
39
+ is_draft = release.fetch('draft')
40
+ is_prerelease = release.fetch('prerelease')
41
+ !is_draft && (include_prereleases || !is_prerelease)
42
+ end.fetch('id')
43
+ end
44
+
45
+ memoize def release
46
+ tag = requested_tag.to_s
47
+ tag = Extconf.instance.debug_mode? ? latest_release : tag_from_gemspec if tag == 'default'
48
+
49
+ github_api.get("releases/#{tag}").body.freeze
50
+ end
51
+
52
+ memoize def release_name = release.fetch('name').freeze
53
+
54
+ memoize def tag_from_gemspec
55
+ class_name = self.class.name
56
+ raise "Can't determine name of anonymous class" unless class_name
57
+
58
+ gem_spec.metadata.fetch("#{class_name.downcase}_library_release_tag")
59
+ end
60
+
61
+ memoize def real_tag_name = release.fetch('tag_name').freeze
62
+
63
+ memoize def commit_hash
64
+ github_api.get("commits/#{real_tag_name}").body.fetch('sha').freeze
65
+ end
66
+
67
+ memoize def assets = release.fetch('assets').to_h { [ _1.fetch('name'), _1 ] }.freeze
68
+
69
+ def matching_asset
70
+ raise NotImplementedError, "This function must be defined in the subclass"
71
+ end
72
+
73
+ memoize def download_size
74
+ matching_asset.fetch('size')
75
+ end
76
+
77
+ memoize def gem_spec
78
+ Bundler.load.specs.find_by_name_and_platform('RuBLE', RUBY_PLATFORM)
79
+ end
80
+
81
+ alias_method :full_gem_path, :gem_full_path
82
+
83
+ memoize def config_data
84
+ release_data = release.dup.tap { _1.delete('assets') }
85
+ release_data['author']&.reject! { |k, _v| k.to_s.end_with?('_url') }
86
+
87
+ asset_data = matching_asset.dup.tap { _1.delete('uploader') }
88
+
89
+ {
90
+ **filter_github_metadata(release_data),
91
+ linkage:,
92
+ requested_tag:,
93
+ commit_hash:,
94
+ release_tag: real_tag_name,
95
+ asset: filter_github_metadata(asset_data),
96
+ repo_url: github_repo_url,
97
+ }.freeze
98
+ end
99
+
100
+ protected
101
+
102
+ memoize def github_api
103
+ options = {
104
+ headers: {
105
+ 'X-GitHub-Api-Version' => GITHUB_API_VERSION,
106
+ 'Accept' => 'application/vnd.github+json',
107
+ },
108
+ request: { timeout: 15 },
109
+ }
110
+ Faraday.new(github_api_base_url, options) do |f|
111
+ f.response :json
112
+ f.request :retry
113
+ f.response :raise_error
114
+ f.response :follow_redirects
115
+ # f.response :logger
116
+ end
117
+ end
118
+
119
+ def filter_github_metadata(data)
120
+ data.reject! do |k, v|
121
+ next true if v == ''
122
+
123
+ k.to_s.match?(/(\A|_)(mentions|reactions|node_id|body|gravatar)(\z|_)/)
124
+ end
125
+ data.transform_values! { _1.is_a?(Hash) ? filter_github_metadata(_1) : _1 }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ module Build
5
+ class SimpleBLE < GithubRepo
6
+ GITHUB_REPO = 'OpenBluetoothToolbox/SimpleBLE'
7
+ EXTRACT_OS_PATH_PRUNE = {
8
+ macos: 'Users/runner/work/SimpleBLE/SimpleBLE/build/install',
9
+ windows: 'install',
10
+ linux: 'SimpleBLE/SimpleBLE/build_simpleble/install',
11
+ }.freeze
12
+
13
+ class << self
14
+ def os_arch
15
+ if OS.mac?
16
+ OS.host_cpu == 'x86_64' ? 'macos-x86_64' : 'macos-arm64'
17
+ elsif OS.linux?
18
+ return 'armv6' if OS.host_cpu.start_with?('arm')
19
+
20
+ OS.bits == 32 ? 'x86' : 'x64'
21
+ elsif OS.windows?
22
+ OS.bits == 32 ? 'Win32' : 'x64'
23
+ else
24
+ raise 'Unsupported OS'
25
+ end
26
+ end
27
+
28
+ def os
29
+ return 'macos' if OS.mac?
30
+ return 'windows' if OS.windows?
31
+ return 'linux' if OS.linux?
32
+
33
+ raise 'Unsupported OS'
34
+ end
35
+
36
+ def os_asset_name = "#{os}-#{os_arch}"
37
+ end
38
+
39
+ # Static makes sense for distribution if we can do it
40
+ memoize def asset_prefix = (static? ? 'simpleble_static' : 'simpleble_shared')
41
+ memoize def asset_name = self.class.os_asset_name
42
+ memoize def asset_filename = "#{asset_prefix}_#{asset_name}.zip".freeze
43
+ memoize def matching_asset = assets.fetch(asset_filename).freeze
44
+
45
+ memoize def extract_src_path
46
+ EXTRACT_OS_PATH_PRUNE.fetch(self.class.os.to_sym).freeze
47
+ end
48
+
49
+ memoize def config_data
50
+ super.dup.tap do |data|
51
+ data[:archive_path] = extract_src_path
52
+ end.freeze
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version'
4
+ require_relative 'build/core_ext'
5
+
6
+ module RuBLE
7
+ module Build
8
+ class << self
9
+ include Memery
10
+ memoize def zeitwerk
11
+ require 'zeitwerk'
12
+ Zeitwerk::Loader.new.tap do |loader|
13
+ loader.inflector.inflect(
14
+ 'cmake' => 'CMake',
15
+ 'ruble' => 'RuBLE',
16
+ 'simpleble' => 'SimpleBLE',
17
+ 'os' => 'OS',
18
+ )
19
+ loader.push_dir("#{__dir__}/build", namespace: RuBLE::Build)
20
+ loader.ignore("#{__dir__}/build/core_ext.rb")
21
+ end
22
+ end
23
+ private :zeitwerk
24
+ end
25
+ end
26
+ end
27
+
28
+ RuBLE::Build.send(:zeitwerk).setup
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuBLE
4
+ VERSION = '0.0.3.alpha'
5
+ BOOST_LIBRARY_RELEASE_TAG = '1.83.0'
6
+ SIMPLEBLE_LIBRARY_RELEASE_TAG = 'v0.6.1'
7
+ end
data/lib/ruble.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ruble/version'
4
+
5
+ require 'zeitwerk'
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.inflector.inflect(
8
+ 'ruble' => 'RuBLE',
9
+ 'simpleble' => 'SimpleBLE',
10
+ )
11
+ loader.ignore("#{__dir__}/tasks")
12
+ loader.ignore("#{__dir__}/ruble/build")
13
+ loader.ignore("#{__dir__}/ruble/build.rb")
14
+ loader.ignore("#{__dir__}/RuBLE.rb")
15
+ # preloading RuBLE::VERSION manually, to avoid issues if RuBLE::Build
16
+ # and its zeitwerk loader are being used, but this loader can't be used
17
+ # due to lack of library file
18
+ loader.ignore("#{__dir__}/ruble/version.rb")
19
+ loader.setup
20
+
21
+ module RuBLE
22
+ # TODO: is this used? (should it be?)
23
+ class Error < StandardError; end
24
+ class << self
25
+ def try_demangle(symbol)
26
+ require 'open3'
27
+ demangled, status = Open3.capture2('c++filt', stdin_data: symbol)
28
+ demangled if status.success?
29
+ rescue Exception # rubocop:disable Lint/RescueException
30
+ nil
31
+ end
32
+ end
33
+
34
+ # Your code goes here...
35
+ end
36
+
37
+ begin
38
+ require_relative 'ruble/ruble.so'
39
+ rescue LoadError => ex
40
+ if (symbol = ex.message[/\A[^:]+: undefined symbol: (\S+) - /, 1])
41
+ demangled = RuBLE.try_demangle(symbol)
42
+ ex.message << "\n\nDemangled symbol: #{demangled}" if demangled
43
+ end
44
+ raise
45
+ end
46
+
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative './pager'
5
+ require_relative './state_hash'
6
+ require_relative './paths'
7
+
8
+ class DevTasks
9
+ include Paths
10
+ include StateHash
11
+
12
+ attr_reader :pager
13
+ def initialize(*flags)
14
+ @flags = flags.map(&:to_sym).to_set
15
+ @pager = Pager.instance
16
+ @pager.start
17
+ end
18
+
19
+ def flag?(name) = @flags.include?(name.to_sym)
20
+ def quiet? = flag?(:quiet)
21
+ def verbose? = !quiet?
22
+ def single? = flag?(:single)
23
+ def nproc = single? ? 1 : Etc.nprocessors
24
+
25
+ def write_hash_file! = HASH_FILE.write(state_hash)
26
+
27
+ def ninja_cmd(target: nil)
28
+ verbose_flag = verbose? ? '-v' : nil
29
+ [ 'ninja', "-j#{nproc}", verbose_flag, target].compact
30
+ end
31
+
32
+ def make_cmd(target: nil)
33
+ verbose_val = verbose? ? 1 : 0
34
+ ['make', "-j#{nproc}", "V=#{verbose_val}", 'CXX=g++-13', target].compact
35
+ end
36
+
37
+
38
+ def pristine!
39
+ HASH_FILE.unlink if HASH_FILE.exist?
40
+ pager.exec_paginated!(ninja_cmd(target: 'clean'), chdir: BUILD_DIR) if NINJA_FILE.exist?
41
+ pager.exec_paginated!(make_cmd(target: 'clean'), chdir: TEST_DIR) if (TEST_DIR / 'Makefile').exist?
42
+ LIB_FILE.unlink if LIB_FILE.exist?
43
+ BUILD_INFO_FILE.unlink if BUILD_INFO_FILE.exist?
44
+ BUILD_DIR.rmtree if BUILD_DIR.exist?
45
+ end
46
+
47
+ def reconfigure!
48
+ pristine!
49
+ pager.exec_paginated!(%W[bundle exec ruby extconf.rb -- --with-debug].shelljoin, chdir: EXT_DIR)
50
+
51
+
52
+ BUILD_DIR.mkpath
53
+ cmd = %W[
54
+ cmake -G Ninja
55
+ -DCMAKE_CXX_COMPILER=#{CMAKE_CXX_COMPILER}
56
+ -DCMAKE_BUILD_TYPE=Debug
57
+ -DCMAKE_INSTALL_CONFIG_NAME=Debug
58
+ #{EXT_DIR_FROM_BUILD_DIR}
59
+ ]
60
+ pager.exec_paginated!(cmd, chdir: BUILD_DIR)
61
+ write_hash_file!
62
+ end
63
+
64
+ def needs_reconfigure? = !HASH_FILE.exist? || HASH_FILE.read.strip != state_hash
65
+ def maybe_reconfigure! = needs_reconfigure? && reconfigure!
66
+
67
+ def build!
68
+ maybe_reconfigure!
69
+ pager.exec_paginated!(make_cmd, chdir: TEST_DIR)
70
+
71
+ pager.exec_paginated!(ninja_cmd, chdir: BUILD_DIR)
72
+ pager.exec_paginated!(ninja_cmd(target: 'install'), chdir: BUILD_DIR)
73
+ end
74
+
75
+ Symbol = Struct.new(:Symbol, *%i[address name file flag defined], keyword_init: true) do
76
+ include Comparable
77
+
78
+ def defined? = self.defined
79
+
80
+ def ignore?
81
+ return true if name =~ /\A(typeinfo|vtable|TLS init function) for /
82
+
83
+ false
84
+ end
85
+
86
+ def err_if_undefined?
87
+ name =~ /Simple(Rb)?BLE/
88
+ end
89
+
90
+ def <=>(other) = name <=> other.name
91
+ end
92
+
93
+ def audit_objfile_symbols!
94
+ defined_symbols = Set.new
95
+ undefined_symbols = Hash.new
96
+ objfiles = OBJFILES_DIR.glob('{**/,}*.o')
97
+ filenames = objfiles.map(&:to_s)
98
+ cmd = %W[nm -A -C --defined-only] + filenames
99
+ _result, out, _err = pager.exec_unpaginated!(cmd, chdir: BUILD_DIR)
100
+ out.strip.split(/[\n\r]+/).each do |line|
101
+ file, _, line = line.partition(/:\s*/)
102
+ addr_str, flag, name = line.strip.split(/\s+/, 3)
103
+ address = addr_str.to_i(16)
104
+ sym = Symbol.new(address:, name:, flag:, file:, defined: true)
105
+ defined_symbols << sym
106
+ end
107
+
108
+ cmd = %W[nm -A -C --undefined-only] + filenames
109
+ _result, out, _err = pager.exec_unpaginated!(cmd, chdir: BUILD_DIR)
110
+ out.strip.split(/[\n\r]+/).each do |line|
111
+ file, line = line.split(/:\s*/,2)
112
+ flag, name = line.strip.split(/\s+/, 2)
113
+ sym = Symbol.new(name:, flag:, file:, defined: false)
114
+ next if sym.ignore?
115
+
116
+ undefined_symbols[name] ||= []
117
+ undefined_symbols[name] << sym
118
+ end
119
+
120
+ defined_symbol_names = defined_symbols.map(&:name).to_set
121
+ undefined_symbol_names = undefined_symbols.keys.to_set
122
+ missing_symbol_names = undefined_symbol_names - defined_symbol_names
123
+ missing_symbols = undefined_symbols.values_at(*missing_symbol_names).flatten
124
+ puts 'The following symbols are not defined in the object files, but should be:'
125
+ missing_symbols.select!(&:err_if_undefined?)
126
+
127
+ missing_symbols.map(&:name).uniq.each { puts _1 }
128
+ end
129
+ end
130
+