rbxx 0.1.0

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +72 -0
  3. data/exe/rbxx +6 -0
  4. data/ext-cmake/FindRuby.cmake +42 -0
  5. data/ext-cmake/rbxx-config.cmake +25 -0
  6. data/include/rbxx/arg.hpp +347 -0
  7. data/include/rbxx/callback.hpp +54 -0
  8. data/include/rbxx/class.hpp +630 -0
  9. data/include/rbxx/data_object.hpp +221 -0
  10. data/include/rbxx/detail/ruby_include.hpp +36 -0
  11. data/include/rbxx/exception.hpp +116 -0
  12. data/include/rbxx/extension.hpp +23 -0
  13. data/include/rbxx/function.hpp +463 -0
  14. data/include/rbxx/module.hpp +69 -0
  15. data/include/rbxx/nogvl.hpp +146 -0
  16. data/include/rbxx/object.hpp +68 -0
  17. data/include/rbxx/operators.hpp +31 -0
  18. data/include/rbxx/policies.hpp +36 -0
  19. data/include/rbxx/protect.hpp +72 -0
  20. data/include/rbxx/rbxx.hpp +20 -0
  21. data/include/rbxx/registry.hpp +145 -0
  22. data/include/rbxx/stl/array.hpp +40 -0
  23. data/include/rbxx/stl/chrono.hpp +26 -0
  24. data/include/rbxx/stl/detail.hpp +60 -0
  25. data/include/rbxx/stl/filesystem.hpp +21 -0
  26. data/include/rbxx/stl/map.hpp +49 -0
  27. data/include/rbxx/stl/optional.hpp +23 -0
  28. data/include/rbxx/stl/set.hpp +30 -0
  29. data/include/rbxx/stl/tuple.hpp +74 -0
  30. data/include/rbxx/stl/variant.hpp +35 -0
  31. data/include/rbxx/stl/vector.hpp +34 -0
  32. data/include/rbxx/stl.hpp +11 -0
  33. data/include/rbxx/type_caster.hpp +247 -0
  34. data/include/rbxx/value.hpp +63 -0
  35. data/include/rbxx/version.hpp +14 -0
  36. data/lib/rbxx/cli.rb +127 -0
  37. data/lib/rbxx/mkmf.rb +144 -0
  38. data/lib/rbxx/rake_tasks.rb +44 -0
  39. data/lib/rbxx/version.rb +5 -0
  40. data/lib/rbxx.rb +8 -0
  41. data/single_include/rbxx/rbxx.hpp +3211 -0
  42. data/templates/CMakeLists.txt.erb +14 -0
  43. data/templates/Gemfile.erb +10 -0
  44. data/templates/README.md.erb +11 -0
  45. data/templates/Rakefile.erb +23 -0
  46. data/templates/ci.yml.erb +21 -0
  47. data/templates/extconf.rb.erb +5 -0
  48. data/templates/extconf_cmake.rb.erb +5 -0
  49. data/templates/extension.cpp.erb +23 -0
  50. data/templates/gemspec.erb +16 -0
  51. data/templates/lib.rb.erb +4 -0
  52. data/templates/release.yml.erb +21 -0
  53. data/templates/test.rb.erb +12 -0
  54. data/templates/version.rb.erb +5 -0
  55. metadata +99 -0
data/lib/rbxx/mkmf.rb ADDED
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rbconfig"
5
+ require "shellwords"
6
+
7
+ module Rbxx
8
+ # Shared mkmf and CMake configuration for extensions built with rbxx.
9
+ module Mkmf
10
+ module_function
11
+
12
+ def include_dir
13
+ File.expand_path("../../include", __dir__)
14
+ end
15
+
16
+ def cmake_config_dir
17
+ File.expand_path("../../ext-cmake", __dir__)
18
+ end
19
+
20
+ def create_makefile(target, include_path: include_dir, debug: debug?, sanitize: sanitize?)
21
+ configure_cpp_flags(include_path, debug:, sanitize:)
22
+ super_create_makefile(target)
23
+ end
24
+
25
+ def create_cmake_makefile(target, source_dir: __dir__, build_dir: "cmake-build")
26
+ target_name = File.basename(target)
27
+ configure, build = cmake_commands(source_dir, build_dir)
28
+ extension = "#{target_name}.#{RbConfig::CONFIG.fetch('DLEXT')}"
29
+ windows = RUBY_PLATFORM.include?("mingw")
30
+ makefile = cmake_makefile(configure, build, build_dir, extension, windows:)
31
+ File.write("Makefile", makefile)
32
+ end
33
+
34
+ def cmake_commands(source_dir, build_dir, cxx: RbConfig::CONFIG.fetch("CXX"),
35
+ platform: RUBY_PLATFORM)
36
+ compiler, *compiler_flags = Shellwords.split(cxx)
37
+ raise ArgumentError, "rbxx: Ruby C++ compiler is not configured" unless compiler
38
+
39
+ configure = ["cmake", "-S", File.expand_path(source_dir), "-B", build_dir,
40
+ "-Drbxx_DIR=#{cmake_config_dir}", "-DRuby_EXECUTABLE=#{RbConfig.ruby}",
41
+ "-DCMAKE_CXX_COMPILER=#{compiler}"]
42
+ configure << "-DCMAKE_CXX_FLAGS_INIT=#{compiler_flags.join(' ')}" unless compiler_flags.empty?
43
+ build = ["cmake", "--build", build_dir, "--config", "Release"]
44
+
45
+ configure.push("-G", "MinGW Makefiles") if platform.include?("mingw")
46
+
47
+ [configure, build]
48
+ end
49
+
50
+ def cmake_makefile(configure, build, build_dir, extension, windows: false)
51
+ built_extension = File.join(build_dir, extension)
52
+ install_copy = if windows
53
+ shell_join(["cmake", "-E", "copy", extension,
54
+ "$(sitearchdir)/#{extension}"], windows: true)
55
+ else
56
+ "cmake -E copy #{Shellwords.escape(extension)} $(sitearchdir)/#{extension}"
57
+ end
58
+ lines = [
59
+ ".PHONY: all clean install", "", "all:", "\t#{shell_join(configure, windows:)}",
60
+ "\t#{shell_join(build, windows:)}",
61
+ "\t#{shell_join(['cmake', '-E', 'copy', built_extension, extension], windows:)}", "",
62
+ "install: all", "\tcmake -E make_directory $(sitearchdir)",
63
+ "\t#{install_copy}", "", "clean:",
64
+ "\t#{shell_join(['cmake', '-E', 'remove_directory', build_dir], windows:)}",
65
+ "\t#{shell_join(['cmake', '-E', 'rm', '-f', extension], windows:)}", ""
66
+ ]
67
+ lines.unshift("unexport MAKE", "") if windows
68
+ lines.join("\n")
69
+ end
70
+
71
+ def shell_join(arguments, windows: false)
72
+ return Shellwords.join(arguments) unless windows
73
+
74
+ arguments.map { |argument| windows_shell_escape(argument) }.join(" ")
75
+ end
76
+
77
+ def windows_shell_escape(argument)
78
+ value = argument.to_s
79
+ return value if value.match?(%r{\A[A-Za-z0-9_+.,:/\\=@-]+\z})
80
+
81
+ escaped = value.gsub(/(\\*)"/) { "#{Regexp.last_match(1) * 2}\\\"" }
82
+ %("#{escaped.sub(/(\\+)\z/) { |slashes| slashes * 2 }}")
83
+ end
84
+
85
+ def debug?
86
+ ENV["RBXX_DEBUG"] == "1"
87
+ end
88
+
89
+ def sanitize?
90
+ ENV["RBXX_SANITIZE"] == "1"
91
+ end
92
+
93
+ def configure_cpp_flags(include_path, debug:, sanitize:)
94
+ compiler_flags(include_path, debug:, sanitize:).each do |name, flags|
95
+ append_global(name, flags)
96
+ end
97
+ end
98
+
99
+ def compiler_flags(include_path, debug:, sanitize:, compiler: msvc? ? :msvc : :gnu)
100
+ return msvc_flags(include_path, debug:) if compiler == :msvc
101
+
102
+ cppflags = " -I#{Shellwords.escape(include_path)}"
103
+ cppflags += " -DRBXX_DEBUG=1" if debug
104
+ optimization = debug ? "-O0 -g" : RbConfig::CONFIG.fetch("optflags", "-O2")
105
+ cxxflags = " #{optimization} -std=c++20 -Wall -Wextra -fvisibility=hidden"
106
+ cxxflags += " -fsanitize=address,undefined -fno-omit-frame-pointer" if sanitize
107
+ ldflags = sanitize ? " -fsanitize=address,undefined" : ""
108
+ { cppflags:, cxxflags:, ldflags: }
109
+ end
110
+
111
+ def msvc_flags(include_path, debug:)
112
+ cppflags = %( /I"#{include_path}")
113
+ cppflags += " /DRBXX_DEBUG=1" if debug
114
+ optimization = debug ? "/Od /Zi" : "/O2"
115
+ { cppflags:, cxxflags: " #{optimization} /std:c++20 /EHsc /utf-8 /W4", ldflags: "" }
116
+ end
117
+
118
+ # mkmf exposes compiler configuration exclusively through these globals.
119
+ # rubocop:disable Style/GlobalVars
120
+ def append_global(name, suffix)
121
+ $CPPFLAGS = $CPPFLAGS.to_s + suffix if name == :cppflags
122
+ $CXXFLAGS = $CXXFLAGS.to_s + suffix if name == :cxxflags
123
+ $LDFLAGS = $LDFLAGS.to_s + suffix if name == :ldflags
124
+ end
125
+ # rubocop:enable Style/GlobalVars
126
+
127
+ def msvc?
128
+ compiler = File.basename(RbConfig::CONFIG.fetch("CC", "").split.first.to_s)
129
+ compiler.match?(/\Acl(?:\.exe)?\z/i) || RUBY_PLATFORM.include?("mswin")
130
+ end
131
+
132
+ def super_create_makefile(target)
133
+ TOPLEVEL_BINDING.receiver.send(:create_makefile, target)
134
+ end
135
+ end
136
+ end
137
+
138
+ def create_rbxx_makefile(target, **options)
139
+ Rbxx::Mkmf.create_makefile(target, **options)
140
+ end
141
+
142
+ def create_rbxx_cmake_makefile(target, **options)
143
+ Rbxx::Mkmf.create_cmake_makefile(target, **options)
144
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ module Rbxx
6
+ # Reusable rake-compiler-dock tasks for the supported native gem platforms.
7
+ module RakeTasks
8
+ extend Rake::DSL
9
+
10
+ PLATFORMS = %w[
11
+ x86_64-linux
12
+ aarch64-linux
13
+ x86_64-darwin
14
+ arm64-darwin
15
+ x64-mingw-ucrt
16
+ ].freeze
17
+
18
+ module_function
19
+
20
+ def install(namespace: :precompiled, command: "bundle install && rake native:%<platform>s gem")
21
+ namespace(namespace) do
22
+ desc "Print precompiled gem build commands without starting containers"
23
+ task :dry_run do
24
+ PLATFORMS.each { |platform| puts build_description(platform, command) }
25
+ end
26
+
27
+ PLATFORMS.each do |platform|
28
+ desc "Build the #{platform} native gem with rake-compiler-dock"
29
+ task platform do
30
+ require "rake_compiler_dock"
31
+ RakeCompilerDock.sh(format(command, platform:), platform:)
32
+ end
33
+ end
34
+
35
+ desc "Build native gems for all supported platforms"
36
+ task all: PLATFORMS
37
+ end
38
+ end
39
+
40
+ def build_description(platform, command)
41
+ "RCD_PLATFORM=#{platform} #{format(command, platform:)}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rbxx
4
+ VERSION = "0.1.0"
5
+ end
data/lib/rbxx.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rbxx/version"
4
+
5
+ # Ruby-side tooling for the rbxx C++ binding library.
6
+ module Rbxx
7
+ class Error < StandardError; end
8
+ end