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