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.
- checksums.yaml +7 -0
- data/.gdbinit +21 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +96 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/CMakeLists.txt +4 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +41 -0
- data/ext/ruble/.gitignore +5 -0
- data/ext/ruble/CMakeLists.txt +157 -0
- data/ext/ruble/RuBLEHelpers.cmake +240 -0
- data/ext/ruble/bindings/Adapter.cpp +193 -0
- data/ext/ruble/bindings/Adapter.hpp +85 -0
- data/ext/ruble/bindings/Characteristic.cpp +171 -0
- data/ext/ruble/bindings/Characteristic.hpp +132 -0
- data/ext/ruble/bindings/Descriptor.cpp +34 -0
- data/ext/ruble/bindings/Descriptor.hpp +69 -0
- data/ext/ruble/bindings/Peripheral.cpp +212 -0
- data/ext/ruble/bindings/Peripheral.hpp +108 -0
- data/ext/ruble/bindings/RuBLE.cpp +115 -0
- data/ext/ruble/bindings/Service.cpp +112 -0
- data/ext/ruble/bindings/Service.hpp +61 -0
- data/ext/ruble/bindings/common.hpp +48 -0
- data/ext/ruble/bindings/globals.cpp +43 -0
- data/ext/ruble/cmake.mk +62 -0
- data/ext/ruble/concerns/CharacteristicValueTracker.cpp +18 -0
- data/ext/ruble/concerns/CharacteristicValueTracker.hpp +40 -0
- data/ext/ruble/concerns/Rubyable.cpp +4 -0
- data/ext/ruble/concerns/Rubyable.hpp +46 -0
- data/ext/ruble/config.h.in +25 -0
- data/ext/ruble/containers/ByteArray.cpp +64 -0
- data/ext/ruble/containers/ByteArray.hpp +161 -0
- data/ext/ruble/containers/Callback.hpp +52 -0
- data/ext/ruble/containers/NamedBitSet.hpp +140 -0
- data/ext/ruble/containers/NamedBitSet.ipp +71 -0
- data/ext/ruble/extconf.rb +30 -0
- data/ext/ruble/management/Registry.cpp +63 -0
- data/ext/ruble/management/Registry.hpp +170 -0
- data/ext/ruble/management/RegistryFactory.hpp +113 -0
- data/ext/ruble/management/RubyQueue.cpp +152 -0
- data/ext/ruble/management/RubyQueue.hpp +69 -0
- data/ext/ruble/modularize.diff +28 -0
- data/ext/ruble/types/SimpleBLE.hpp +21 -0
- data/ext/ruble/types/declarations.hpp +91 -0
- data/ext/ruble/types/helpers.hpp +12 -0
- data/ext/ruble/types/ruby.hpp +36 -0
- data/ext/ruble/types/stl.hpp +41 -0
- data/ext/ruble/utils/RubyCallbackTraits.cpp +28 -0
- data/ext/ruble/utils/RubyCallbackTraits.hpp +48 -0
- data/ext/ruble/utils/async.cpp +10 -0
- data/ext/ruble/utils/async.hpp +76 -0
- data/ext/ruble/utils/containers.hpp +41 -0
- data/ext/ruble/utils/exception_handling.cpp +50 -0
- data/ext/ruble/utils/exception_handling.hpp +53 -0
- data/ext/ruble/utils/garbage_collection.cpp +82 -0
- data/ext/ruble/utils/garbage_collection.hpp +22 -0
- data/ext/ruble/utils/hash.cpp +83 -0
- data/ext/ruble/utils/hash.hpp +52 -0
- data/ext/ruble/utils/hexadecimal.hpp +116 -0
- data/ext/ruble/utils/human_type_names.hpp +38 -0
- data/ext/ruble/utils/inspection.cpp +24 -0
- data/ext/ruble/utils/inspection.hpp +108 -0
- data/ext/ruble/utils/ruby.hpp +103 -0
- data/ext/ruble/utils/ruby_context.hpp +73 -0
- data/lib/ruble/build/.rubocop.yml +19 -0
- data/lib/ruble/build/boost.rb +34 -0
- data/lib/ruble/build/cmake.rb +134 -0
- data/lib/ruble/build/core_ext.rb +5 -0
- data/lib/ruble/build/data/bundler.rb +24 -0
- data/lib/ruble/build/data/extension.rb +101 -0
- data/lib/ruble/build/data/os.rb +21 -0
- data/lib/ruble/build/data/rice.rb +24 -0
- data/lib/ruble/build/data.rb +22 -0
- data/lib/ruble/build/extconf.rb +76 -0
- data/lib/ruble/build/github_repo.rb +129 -0
- data/lib/ruble/build/simpleble.rb +56 -0
- data/lib/ruble/build.rb +28 -0
- data/lib/ruble/version.rb +7 -0
- data/lib/ruble.rb +46 -0
- data/lib/tasks/dev/dev_tasks.rb +130 -0
- data/lib/tasks/dev/pager.rb +218 -0
- data/lib/tasks/dev/paths.rb +30 -0
- data/lib/tasks/dev/state_hash.rb +65 -0
- data/lib/tasks/dev.rake +41 -0
- data/lib/tasks/simpleble.rake +29 -0
- data/sig/rubble.rbs +4 -0
- 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
|
data/lib/ruble/build.rb
ADDED
@@ -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
|
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
|
+
|