ruble 0.0.3.alpha

Sign up to get free protection for your applications and to get access to all the features.
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
+