rbs 3.9.4 → 4.1.1
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 +4 -4
- data/.clang-format +75 -0
- data/.clangd +2 -0
- data/.dockerignore +37 -0
- data/.github/dependabot.yml +16 -14
- data/.github/workflows/bundle-update.yml +63 -0
- data/.github/workflows/c-check.yml +66 -0
- data/.github/workflows/comments.yml +5 -3
- data/.github/workflows/dependabot.yml +2 -2
- data/.github/workflows/jruby.yml +79 -0
- data/.github/workflows/release-gems.yml +164 -0
- data/.github/workflows/ruby.yml +84 -30
- data/.github/workflows/rust.yml +186 -0
- data/.github/workflows/truffleruby.yml +54 -0
- data/.github/workflows/typecheck.yml +5 -2
- data/.github/workflows/wasm.yml +55 -0
- data/.github/workflows/windows.yml +9 -3
- data/.gitignore +16 -0
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +430 -0
- data/Dockerfile.jruby +53 -0
- data/README.md +41 -4
- data/Rakefile +777 -60
- data/Steepfile +11 -0
- data/config.yml +660 -62
- data/core/array.rbs +541 -398
- data/core/basic_object.rbs +9 -8
- data/core/binding.rbs +0 -2
- data/core/builtin.rbs +9 -8
- data/core/class.rbs +11 -8
- data/core/comparable.rbs +55 -34
- data/core/complex.rbs +104 -78
- data/core/dir.rbs +61 -49
- data/core/encoding.rbs +12 -15
- data/core/enumerable.rbs +297 -196
- data/core/enumerator/arithmetic_sequence.rbs +70 -0
- data/core/enumerator/product.rbs +5 -5
- data/core/enumerator.rbs +91 -28
- data/core/errno.rbs +11 -2
- data/core/errors.rbs +58 -29
- data/core/exception.rbs +13 -13
- data/core/fiber.rbs +74 -54
- data/core/file.rbs +260 -1151
- data/core/file_constants.rbs +463 -0
- data/core/file_stat.rbs +534 -0
- data/core/file_test.rbs +3 -3
- data/core/float.rbs +257 -116
- data/core/gc.rbs +425 -281
- data/core/hash.rbs +1151 -829
- data/core/integer.rbs +156 -195
- data/core/io/buffer.rbs +53 -42
- data/core/io/wait.rbs +13 -35
- data/core/io.rbs +220 -154
- data/core/kernel.rbs +239 -163
- data/core/marshal.rbs +4 -4
- data/core/match_data.rbs +16 -14
- data/core/math.rbs +107 -66
- data/core/method.rbs +69 -33
- data/core/module.rbs +302 -150
- data/core/nil_class.rbs +7 -6
- data/core/numeric.rbs +77 -63
- data/core/object.rbs +9 -11
- data/core/object_space/weak_key_map.rbs +7 -7
- data/core/object_space.rbs +30 -23
- data/core/pathname.rbs +1312 -0
- data/core/proc.rbs +95 -58
- data/core/process.rbs +222 -202
- data/core/ractor.rbs +364 -518
- data/core/random.rbs +21 -3
- data/core/range.rbs +181 -79
- data/core/rational.rbs +60 -89
- data/core/rbs/ops.rbs +154 -0
- data/core/rbs/unnamed/argf.rbs +63 -56
- data/core/rbs/unnamed/env_class.rbs +19 -14
- data/core/rbs/unnamed/main_class.rbs +123 -0
- data/core/rbs/unnamed/random.rbs +11 -118
- data/core/regexp.rbs +258 -214
- data/core/ruby.rbs +53 -0
- data/core/ruby_vm.rbs +78 -34
- data/core/rubygems/config_file.rbs +5 -5
- data/core/rubygems/errors.rbs +6 -70
- data/core/rubygems/requirement.rbs +5 -15
- data/core/rubygems/rubygems.rbs +18 -81
- data/core/rubygems/specification.rbs +8 -0
- data/core/rubygems/version.rbs +2 -163
- data/core/set.rbs +493 -363
- data/core/signal.rbs +26 -16
- data/core/string.rbs +3234 -1285
- data/core/struct.rbs +43 -42
- data/core/symbol.rbs +41 -34
- data/core/thread.rbs +139 -83
- data/core/time.rbs +81 -50
- data/core/trace_point.rbs +41 -35
- data/core/true_class.rbs +2 -2
- data/core/unbound_method.rbs +24 -16
- data/core/warning.rbs +7 -7
- data/docs/CONTRIBUTING.md +2 -1
- data/docs/aliases.md +79 -0
- data/docs/collection.md +3 -3
- data/docs/config.md +171 -0
- data/docs/encoding.md +56 -0
- data/docs/gem.md +0 -1
- data/docs/inline.md +634 -0
- data/docs/rbs_by_example.md +20 -20
- data/docs/release.md +151 -0
- data/docs/rust.md +96 -0
- data/docs/sigs.md +3 -3
- data/docs/syntax.md +48 -18
- data/docs/type_fingerprint.md +21 -0
- data/docs/wasm_serialization.md +80 -0
- data/exe/rbs +1 -1
- data/ext/rbs_extension/ast_translation.c +1855 -0
- data/ext/rbs_extension/ast_translation.h +41 -0
- data/ext/rbs_extension/class_constants.c +187 -0
- data/{include/rbs/constants.h → ext/rbs_extension/class_constants.h} +23 -1
- data/ext/rbs_extension/compat.h +10 -0
- data/ext/rbs_extension/extconf.rb +26 -1
- data/ext/rbs_extension/legacy_location.c +299 -0
- data/ext/rbs_extension/legacy_location.h +82 -0
- data/ext/rbs_extension/main.c +603 -23
- data/ext/rbs_extension/rbs_extension.h +6 -21
- data/ext/rbs_extension/rbs_string_bridging.c +9 -0
- data/ext/rbs_extension/rbs_string_bridging.h +24 -0
- data/include/rbs/ast.h +1047 -0
- data/include/rbs/defines.h +99 -0
- data/include/rbs/lexer.h +207 -0
- data/include/rbs/location.h +40 -0
- data/include/rbs/parser.h +153 -0
- data/include/rbs/serialize.h +39 -0
- data/include/rbs/string.h +47 -0
- data/include/rbs/util/rbs_allocator.h +59 -0
- data/include/rbs/util/rbs_assert.h +20 -0
- data/include/rbs/util/rbs_buffer.h +83 -0
- data/include/rbs/util/rbs_constant_pool.h +6 -70
- data/include/rbs/util/rbs_encoding.h +282 -0
- data/include/rbs/util/rbs_unescape.h +24 -0
- data/include/rbs.h +9 -2
- data/lib/rbs/annotate/formatter.rb +3 -13
- data/lib/rbs/annotate/rdoc_annotator.rb +30 -32
- data/lib/rbs/annotate/rdoc_source.rb +1 -1
- data/lib/rbs/ast/annotation.rb +1 -1
- data/lib/rbs/ast/comment.rb +1 -1
- data/lib/rbs/ast/declarations.rb +10 -10
- data/lib/rbs/ast/members.rb +14 -14
- data/lib/rbs/ast/ruby/annotations.rb +451 -0
- data/lib/rbs/ast/ruby/comment_block.rb +247 -0
- data/lib/rbs/ast/ruby/declarations.rb +291 -0
- data/lib/rbs/ast/ruby/helpers/constant_helper.rb +28 -0
- data/lib/rbs/ast/ruby/helpers/location_helper.rb +15 -0
- data/lib/rbs/ast/ruby/members.rb +762 -0
- data/lib/rbs/ast/type_param.rb +24 -4
- data/lib/rbs/buffer.rb +142 -20
- data/lib/rbs/cli/diff.rb +16 -15
- data/lib/rbs/cli/validate.rb +63 -126
- data/lib/rbs/cli.rb +59 -29
- data/lib/rbs/collection/config/lockfile_generator.rb +28 -3
- data/lib/rbs/collection/sources/git.rb +7 -0
- data/lib/rbs/definition.rb +6 -1
- data/lib/rbs/definition_builder/ancestor_builder.rb +121 -65
- data/lib/rbs/definition_builder/method_builder.rb +74 -33
- data/lib/rbs/definition_builder.rb +177 -20
- data/lib/rbs/diff.rb +7 -1
- data/lib/rbs/environment/class_entry.rb +69 -0
- data/lib/rbs/environment/module_entry.rb +66 -0
- data/lib/rbs/environment.rb +410 -215
- data/lib/rbs/environment_loader.rb +2 -8
- data/lib/rbs/errors.rb +31 -21
- data/lib/rbs/inline_parser/comment_association.rb +117 -0
- data/lib/rbs/inline_parser.rb +568 -0
- data/lib/rbs/location_aux.rb +36 -4
- data/lib/rbs/locator.rb +5 -1
- data/lib/rbs/method_type.rb +5 -3
- data/lib/rbs/namespace.rb +47 -18
- data/lib/rbs/parser_aux.rb +37 -7
- data/lib/rbs/prototype/helpers.rb +57 -0
- data/lib/rbs/prototype/rb.rb +3 -28
- data/lib/rbs/prototype/rbi.rb +196 -45
- data/lib/rbs/prototype/runtime.rb +12 -2
- data/lib/rbs/resolver/constant_resolver.rb +2 -2
- data/lib/rbs/resolver/type_name_resolver.rb +120 -44
- data/lib/rbs/rewriter.rb +70 -0
- data/lib/rbs/source.rb +99 -0
- data/lib/rbs/subtractor.rb +7 -4
- data/lib/rbs/test/type_check.rb +25 -3
- data/lib/rbs/type_name.rb +34 -21
- data/lib/rbs/types.rb +91 -79
- data/lib/rbs/unit_test/convertibles.rb +1 -0
- data/lib/rbs/unit_test/type_assertions.rb +44 -8
- data/lib/rbs/validator.rb +2 -2
- data/lib/rbs/version.rb +1 -1
- data/lib/rbs/wasm/deserializer.rb +213 -0
- data/lib/rbs/wasm/location.rb +61 -0
- data/lib/rbs/wasm/parser.rb +137 -0
- data/lib/rbs/wasm/runtime.rb +196 -0
- data/lib/rbs/wasm/serialization_schema.rb +110 -0
- data/lib/rbs.rb +25 -2
- data/lib/rbs_jars.rb +39 -0
- data/lib/rdoc/discover.rb +1 -1
- data/lib/rdoc_plugin/parser.rb +8 -3
- data/rbs.gemspec +37 -5
- data/schema/typeParam.json +17 -1
- data/sig/ancestor_builder.rbs +1 -1
- data/sig/annotate/formatter.rbs +2 -2
- data/sig/annotate/rdoc_annotater.rbs +13 -10
- data/sig/ast/ruby/annotations.rbs +470 -0
- data/sig/ast/ruby/comment_block.rbs +127 -0
- data/sig/ast/ruby/declarations.rbs +158 -0
- data/sig/ast/ruby/helpers/constant_helper.rbs +11 -0
- data/sig/ast/ruby/helpers/location_helper.rbs +15 -0
- data/sig/ast/ruby/members.rbs +198 -0
- data/sig/buffer.rbs +81 -5
- data/sig/cli/diff.rbs +5 -11
- data/sig/cli/validate.rbs +12 -8
- data/sig/cli.rbs +18 -18
- data/sig/collection/config/lockfile_generator.rbs +2 -0
- data/sig/definition.rbs +6 -0
- data/sig/definition_builder.rbs +3 -1
- data/sig/environment/class_entry.rbs +50 -0
- data/sig/environment/module_entry.rbs +50 -0
- data/sig/environment.rbs +94 -87
- data/sig/errors.rbs +26 -20
- data/sig/inline_parser/comment_association.rbs +71 -0
- data/sig/inline_parser.rbs +126 -0
- data/sig/location.rbs +32 -7
- data/sig/locator.rbs +0 -2
- data/sig/manifest.yaml +0 -2
- data/sig/method_builder.rbs +9 -4
- data/sig/namespace.rbs +20 -5
- data/sig/parser.rbs +77 -13
- data/sig/prototype/helpers.rbs +2 -0
- data/sig/prototype/rbi.rbs +33 -4
- data/sig/resolver/type_name_resolver.rbs +36 -10
- data/sig/rewriter.rbs +45 -0
- data/sig/source.rbs +48 -0
- data/sig/type_param.rbs +13 -8
- data/sig/typename.rbs +15 -5
- data/sig/types.rbs +10 -8
- data/sig/unit_test/spy.rbs +0 -8
- data/sig/unit_test/type_assertions.rbs +15 -0
- data/sig/wasm/deserializer.rbs +66 -0
- data/sig/wasm/serialization_schema.rbs +13 -0
- data/src/ast.c +1628 -0
- data/src/lexer.c +3221 -0
- data/src/lexer.re +155 -0
- data/src/lexstate.c +221 -0
- data/src/location.c +31 -0
- data/src/parser.c +4258 -0
- data/src/serialize.c +958 -0
- data/src/string.c +41 -0
- data/src/util/rbs_allocator.c +171 -0
- data/src/util/rbs_assert.c +19 -0
- data/src/util/rbs_buffer.c +54 -0
- data/src/util/rbs_constant_pool.c +18 -92
- data/src/util/rbs_encoding.c +21308 -0
- data/src/util/rbs_unescape.c +167 -0
- data/stdlib/abbrev/0/array.rbs +1 -1
- data/stdlib/bigdecimal/0/big_decimal.rbs +116 -98
- data/stdlib/bigdecimal-math/0/big_math.rbs +169 -8
- data/stdlib/cgi/0/core.rbs +9 -393
- data/stdlib/cgi/0/manifest.yaml +1 -0
- data/stdlib/cgi-escape/0/escape.rbs +171 -0
- data/stdlib/coverage/0/coverage.rbs +7 -4
- data/stdlib/csv/0/csv.rbs +5 -5
- data/stdlib/date/0/date.rbs +92 -79
- data/stdlib/date/0/date_time.rbs +25 -24
- data/stdlib/delegate/0/delegator.rbs +11 -7
- data/stdlib/did_you_mean/0/did_you_mean.rbs +17 -16
- data/stdlib/digest/0/digest.rbs +117 -1
- data/stdlib/erb/0/erb.rbs +748 -347
- data/stdlib/etc/0/etc.rbs +73 -54
- data/stdlib/fileutils/0/fileutils.rbs +179 -160
- data/stdlib/forwardable/0/forwardable.rbs +13 -10
- data/stdlib/io-console/0/io-console.rbs +2 -2
- data/stdlib/ipaddr/0/ipaddr.rbs +0 -5
- data/stdlib/json/0/json.rbs +232 -185
- data/stdlib/monitor/0/monitor.rbs +5 -5
- data/stdlib/net-http/0/net-http.rbs +162 -134
- data/stdlib/objspace/0/objspace.rbs +17 -34
- data/stdlib/open-uri/0/open-uri.rbs +48 -8
- data/stdlib/open3/0/open3.rbs +469 -10
- data/stdlib/openssl/0/openssl.rbs +521 -397
- data/stdlib/optparse/0/optparse.rbs +26 -17
- data/stdlib/pathname/0/pathname.rbs +11 -1381
- data/stdlib/pp/0/pp.rbs +9 -8
- data/stdlib/prettyprint/0/prettyprint.rbs +7 -7
- data/stdlib/pstore/0/pstore.rbs +35 -30
- data/stdlib/psych/0/psych.rbs +65 -12
- data/stdlib/psych/0/store.rbs +2 -4
- data/stdlib/pty/0/pty.rbs +9 -6
- data/stdlib/random-formatter/0/random-formatter.rbs +277 -0
- data/stdlib/rdoc/0/code_object.rbs +4 -3
- data/stdlib/rdoc/0/comment.rbs +2 -0
- data/stdlib/rdoc/0/options.rbs +76 -0
- data/stdlib/rdoc/0/parser.rbs +1 -1
- data/stdlib/rdoc/0/rdoc.rbs +7 -5
- data/stdlib/rdoc/0/store.rbs +2 -2
- data/stdlib/resolv/0/resolv.rbs +26 -69
- data/stdlib/ripper/0/ripper.rbs +25 -19
- data/stdlib/securerandom/0/manifest.yaml +2 -0
- data/stdlib/securerandom/0/securerandom.rbs +7 -20
- data/stdlib/shellwords/0/shellwords.rbs +3 -3
- data/stdlib/singleton/0/singleton.rbs +3 -0
- data/stdlib/socket/0/addrinfo.rbs +9 -9
- data/stdlib/socket/0/basic_socket.rbs +3 -3
- data/stdlib/socket/0/ip_socket.rbs +10 -8
- data/stdlib/socket/0/socket.rbs +23 -10
- data/stdlib/socket/0/tcp_server.rbs +1 -1
- data/stdlib/socket/0/tcp_socket.rbs +11 -3
- data/stdlib/socket/0/udp_socket.rbs +1 -1
- data/stdlib/socket/0/unix_server.rbs +1 -1
- data/stdlib/stringio/0/stringio.rbs +1209 -95
- data/stdlib/strscan/0/string_scanner.rbs +101 -80
- data/stdlib/tempfile/0/manifest.yaml +3 -0
- data/stdlib/tempfile/0/tempfile.rbs +25 -21
- data/stdlib/time/0/time.rbs +8 -6
- data/stdlib/timeout/0/timeout.rbs +58 -7
- data/stdlib/tsort/0/cyclic.rbs +4 -1
- data/stdlib/tsort/0/interfaces.rbs +8 -8
- data/stdlib/tsort/0/tsort.rbs +16 -15
- data/stdlib/uri/0/common.rbs +42 -20
- data/stdlib/uri/0/file.rbs +3 -3
- data/stdlib/uri/0/generic.rbs +21 -18
- data/stdlib/uri/0/http.rbs +2 -2
- data/stdlib/uri/0/ldap.rbs +2 -2
- data/stdlib/uri/0/mailto.rbs +3 -3
- data/stdlib/uri/0/rfc2396_parser.rbs +12 -12
- data/stdlib/zlib/0/deflate.rbs +4 -3
- data/stdlib/zlib/0/gzip_reader.rbs +8 -8
- data/stdlib/zlib/0/gzip_writer.rbs +14 -12
- data/stdlib/zlib/0/inflate.rbs +1 -1
- data/stdlib/zlib/0/need_dict.rbs +1 -1
- data/wasm/README.md +60 -0
- data/wasm/rbs_wasm.c +423 -0
- metadata +131 -19
- data/ext/rbs_extension/lexer.c +0 -2728
- data/ext/rbs_extension/lexer.h +0 -179
- data/ext/rbs_extension/lexer.re +0 -147
- data/ext/rbs_extension/lexstate.c +0 -175
- data/ext/rbs_extension/location.c +0 -325
- data/ext/rbs_extension/location.h +0 -85
- data/ext/rbs_extension/parser.c +0 -2982
- data/ext/rbs_extension/parser.h +0 -18
- data/ext/rbs_extension/parserstate.c +0 -411
- data/ext/rbs_extension/parserstate.h +0 -163
- data/ext/rbs_extension/unescape.c +0 -32
- data/include/rbs/ruby_objs.h +0 -72
- data/src/constants.c +0 -153
- data/src/ruby_objs.c +0 -799
data/Rakefile
CHANGED
|
@@ -11,42 +11,137 @@ bin = File.join(__dir__, "bin")
|
|
|
11
11
|
|
|
12
12
|
Rake::ExtensionTask.new("rbs_extension")
|
|
13
13
|
|
|
14
|
+
compile_task = Rake::Task[:compile]
|
|
15
|
+
|
|
16
|
+
task :setup_extconf_compile_commands_json do
|
|
17
|
+
ENV["COMPILE_COMMANDS_JSON"] = "1"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
compile_task.prerequisites.unshift(:setup_extconf_compile_commands_json)
|
|
21
|
+
|
|
14
22
|
test_config = lambda do |t|
|
|
15
23
|
t.libs << "test"
|
|
16
24
|
t.libs << "lib"
|
|
17
25
|
t.test_files = FileList["test/**/*_test.rb"].reject do |path|
|
|
18
26
|
path =~ %r{test/stdlib/}
|
|
19
27
|
end
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
unless Gem.win_platform?
|
|
25
|
-
begin
|
|
26
|
-
require "ruby_memcheck"
|
|
27
|
-
|
|
28
|
-
namespace :test do
|
|
29
|
-
RubyMemcheck::TestTask.new(valgrind: :compile, &test_config)
|
|
28
|
+
if defined?(RubyMemcheck)
|
|
29
|
+
if t.is_a?(RubyMemcheck::TestTask)
|
|
30
|
+
t.verbose = true
|
|
31
|
+
t.options = '-v'
|
|
30
32
|
end
|
|
31
|
-
rescue LoadError => exn
|
|
32
|
-
STDERR.puts "🚨🚨🚨🚨 Skipping RubyMemcheck: #{exn.inspect} 🚨🚨🚨🚨"
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
if RUBY_ENGINE == "jruby"
|
|
37
|
+
# JRuby runs the parser in WebAssembly instead of the C extension, so there is
|
|
38
|
+
# nothing to compile. The wasm runtime must be assembled first with
|
|
39
|
+
# `rake wasm:jruby_setup` (which needs CRuby + the WASI SDK).
|
|
40
|
+
Rake::TestTask.new(:test, &test_config)
|
|
41
|
+
else
|
|
42
|
+
Rake::TestTask.new(test: :compile, &test_config)
|
|
43
|
+
end
|
|
44
|
+
|
|
36
45
|
multitask :default => [:test, :stdlib_test, :typecheck_test, :rubocop, :validate, :test_doc]
|
|
37
46
|
|
|
38
47
|
task :lexer do
|
|
39
|
-
sh "re2c -W --no-generation-date -o
|
|
48
|
+
sh "re2c -W --no-generation-date -o src/lexer.c src/lexer.re"
|
|
49
|
+
sh "clang-format -i -style=file src/lexer.c"
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
task :confirm_lexer => :lexer do
|
|
43
53
|
puts "Testing if lexer.c is updated with respect to lexer.re"
|
|
44
|
-
sh "git diff --exit-code
|
|
54
|
+
sh "git diff --exit-code src/lexer.c"
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
task :confirm_templates => :templates do
|
|
48
|
-
puts "Testing if generated code
|
|
49
|
-
|
|
58
|
+
puts "Testing if generated code is updated with respect to templates"
|
|
59
|
+
|
|
60
|
+
# Every template generates the file it is named after: `templates/<path>.erb` generates `<path>`.
|
|
61
|
+
generated = Dir.glob("templates/**/*.erb").sort.map { _1.delete_prefix("templates/").delete_suffix(".erb") }
|
|
62
|
+
|
|
63
|
+
missing = generated.reject { File.exist?(_1) }
|
|
64
|
+
raise "Templates without a generated file: #{missing.join(", ")}. Is the `templates` task missing an entry?" unless missing.empty?
|
|
65
|
+
|
|
66
|
+
sh "git diff --exit-code -- #{generated.join(" ")}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Task to format C code using clang-format
|
|
70
|
+
namespace :format do
|
|
71
|
+
dirs = ["src", "ext", "include"]
|
|
72
|
+
|
|
73
|
+
# Find all C source and header files
|
|
74
|
+
files = `find #{dirs.join(" ")} -type f \\( -name "*.c" -o -name "*.h" \\)`.split("\n")
|
|
75
|
+
|
|
76
|
+
desc "Format C source files using clang-format"
|
|
77
|
+
task :c do
|
|
78
|
+
puts "Formatting C files..."
|
|
79
|
+
|
|
80
|
+
# Check if clang-format is installed
|
|
81
|
+
unless system("which clang-format > /dev/null 2>&1")
|
|
82
|
+
abort "Error: clang-format not found. Please install clang-format first."
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if files.empty?
|
|
86
|
+
puts "No C files found to format"
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
puts "Found #{files.length} files to format (excluding generated files)"
|
|
91
|
+
|
|
92
|
+
exit_status = 0
|
|
93
|
+
files.each do |file|
|
|
94
|
+
puts "Formatting #{file}"
|
|
95
|
+
unless system("clang-format -i -style=file #{file}")
|
|
96
|
+
puts "❌ Error formatting #{file}"
|
|
97
|
+
exit_status = 1
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
exit exit_status unless exit_status == 0
|
|
102
|
+
puts "✅ All files formatted successfully"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "Check if C source files are properly formatted"
|
|
106
|
+
task :c_check do
|
|
107
|
+
puts "Checking C file formatting..."
|
|
108
|
+
|
|
109
|
+
# Check if clang-format is installed
|
|
110
|
+
unless system("which clang-format > /dev/null 2>&1")
|
|
111
|
+
abort "Error: clang-format not found. Please install clang-format first."
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if files.empty?
|
|
115
|
+
puts "No C files found to check"
|
|
116
|
+
next
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
puts "Found #{files.length} files to check (excluding generated files)"
|
|
120
|
+
|
|
121
|
+
needs_format = false
|
|
122
|
+
files.each do |file|
|
|
123
|
+
formatted = `clang-format -style=file #{file}`
|
|
124
|
+
original = File.read(file)
|
|
125
|
+
|
|
126
|
+
if formatted != original
|
|
127
|
+
puts "❌ #{file} needs formatting"
|
|
128
|
+
puts "Diff:"
|
|
129
|
+
# Save formatted version to temp file and run diff
|
|
130
|
+
temp_file = "#{file}.formatted"
|
|
131
|
+
File.write(temp_file, formatted)
|
|
132
|
+
system("diff -u #{file} #{temp_file}")
|
|
133
|
+
File.unlink(temp_file)
|
|
134
|
+
needs_format = true
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if needs_format
|
|
139
|
+
warn "Some files need formatting. Run 'rake format:c' to format them."
|
|
140
|
+
exit 1
|
|
141
|
+
else
|
|
142
|
+
puts "✅ All files are properly formatted"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
50
145
|
end
|
|
51
146
|
|
|
52
147
|
rule ".c" => ".re" do |t|
|
|
@@ -59,6 +154,9 @@ end
|
|
|
59
154
|
rule %r{^include/(.*)\.c} => 'templates/%X.c.erb' do |t|
|
|
60
155
|
puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
|
|
61
156
|
end
|
|
157
|
+
rule %r{^ext/(.*)\.c} => 'templates/%X.c.erb' do |t|
|
|
158
|
+
puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
|
|
159
|
+
end
|
|
62
160
|
|
|
63
161
|
task :annotate do
|
|
64
162
|
sh "bin/generate_docs.sh"
|
|
@@ -70,17 +168,26 @@ task :confirm_annotation do
|
|
|
70
168
|
end
|
|
71
169
|
|
|
72
170
|
task :templates do
|
|
73
|
-
sh "#{ruby} templates/template.rb
|
|
74
|
-
sh "#{ruby} templates/template.rb
|
|
75
|
-
|
|
76
|
-
sh "#{ruby} templates/template.rb
|
|
171
|
+
sh "#{ruby} templates/template.rb ext/rbs_extension/ast_translation.h"
|
|
172
|
+
sh "#{ruby} templates/template.rb ext/rbs_extension/ast_translation.c"
|
|
173
|
+
|
|
174
|
+
sh "#{ruby} templates/template.rb ext/rbs_extension/class_constants.h"
|
|
175
|
+
sh "#{ruby} templates/template.rb ext/rbs_extension/class_constants.c"
|
|
176
|
+
|
|
177
|
+
sh "#{ruby} templates/template.rb include/rbs/ast.h"
|
|
178
|
+
sh "#{ruby} templates/template.rb src/ast.c"
|
|
179
|
+
|
|
180
|
+
sh "#{ruby} templates/template.rb include/rbs/serialize.h"
|
|
181
|
+
sh "#{ruby} templates/template.rb src/serialize.c"
|
|
182
|
+
sh "#{ruby} templates/template.rb lib/rbs/wasm/serialization_schema.rb"
|
|
183
|
+
|
|
184
|
+
# Format the generated files
|
|
185
|
+
Rake::Task["format:c"].invoke
|
|
77
186
|
end
|
|
78
187
|
|
|
79
|
-
task :compile => "ext/rbs_extension/
|
|
80
|
-
task :compile => "
|
|
81
|
-
task :compile => "
|
|
82
|
-
task :compile => "src/constants.c"
|
|
83
|
-
task :compile => "src/ruby_objs.c"
|
|
188
|
+
task :compile => "ext/rbs_extension/class_constants.h"
|
|
189
|
+
task :compile => "ext/rbs_extension/class_constants.c"
|
|
190
|
+
task :compile => "src/lexer.c"
|
|
84
191
|
|
|
85
192
|
task :test_doc do
|
|
86
193
|
files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
@@ -93,7 +200,7 @@ end
|
|
|
93
200
|
task :validate => :compile do
|
|
94
201
|
require 'yaml'
|
|
95
202
|
|
|
96
|
-
sh "#{ruby} #{rbs} validate
|
|
203
|
+
sh "#{ruby} #{rbs} validate"
|
|
97
204
|
|
|
98
205
|
libs = FileList["stdlib/*"].map {|path| File.basename(path).to_s }
|
|
99
206
|
|
|
@@ -113,8 +220,18 @@ task :validate => :compile do
|
|
|
113
220
|
libs << "rbs"
|
|
114
221
|
end
|
|
115
222
|
|
|
223
|
+
libs.delete("bigdecimal-math") or raise
|
|
224
|
+
libs.delete("bigdecimal") or raise
|
|
225
|
+
|
|
116
226
|
libs.each do |lib|
|
|
117
|
-
|
|
227
|
+
args = ["-r", lib]
|
|
228
|
+
|
|
229
|
+
if lib == "rbs"
|
|
230
|
+
args << "-r" << "prism"
|
|
231
|
+
args << "-r" << "logger"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
sh "#{ruby} #{rbs} #{args.join(' ')} validate"
|
|
118
235
|
end
|
|
119
236
|
end
|
|
120
237
|
|
|
@@ -126,7 +243,7 @@ end
|
|
|
126
243
|
|
|
127
244
|
task :stdlib_test => :compile do
|
|
128
245
|
test_files = FileList["test/stdlib/**/*_test.rb"].reject do |path|
|
|
129
|
-
path =~ %r{Ractor} || path =~ %r{Encoding} || path =~ %r{
|
|
246
|
+
path =~ %r{Ractor} || path =~ %r{Encoding} || path =~ %r{CGI-escape_test}
|
|
130
247
|
end
|
|
131
248
|
|
|
132
249
|
if ENV["RANDOMIZE_STDLIB_TEST_ORDER"] == "true"
|
|
@@ -135,7 +252,7 @@ task :stdlib_test => :compile do
|
|
|
135
252
|
|
|
136
253
|
sh "#{ruby} -Ilib #{bin}/test_runner.rb #{test_files.join(' ')}"
|
|
137
254
|
# TODO: Ractor tests need to be run in a separate process
|
|
138
|
-
sh "#{ruby} -Ilib #{bin}/test_runner.rb test/stdlib/
|
|
255
|
+
sh "#{ruby} -Ilib #{bin}/test_runner.rb test/stdlib/CGI-escape_test.rb"
|
|
139
256
|
sh "#{ruby} -Ilib #{bin}/test_runner.rb test/stdlib/Ractor_test.rb"
|
|
140
257
|
sh "#{ruby} -Ilib #{bin}/test_runner.rb test/stdlib/Encoding_test.rb"
|
|
141
258
|
end
|
|
@@ -228,7 +345,7 @@ namespace :generate do
|
|
|
228
345
|
class <%= target %>SingletonTest < Test::Unit::TestCase
|
|
229
346
|
include TestHelper
|
|
230
347
|
|
|
231
|
-
# library "
|
|
348
|
+
# library "logger", "securerandom" # Declare library signatures to load
|
|
232
349
|
testing "singleton(::<%= target %>)"
|
|
233
350
|
|
|
234
351
|
<%- class_methods.each do |method_name, definition| -%>
|
|
@@ -247,7 +364,7 @@ namespace :generate do
|
|
|
247
364
|
class <%= target %>Test < Test::Unit::TestCase
|
|
248
365
|
include TestHelper
|
|
249
366
|
|
|
250
|
-
# library "
|
|
367
|
+
# library "logger", "securerandom" # Declare library signatures to load
|
|
251
368
|
testing "::<%= target %>"
|
|
252
369
|
|
|
253
370
|
<%- instance_methods.each do |method_name, definition| -%>
|
|
@@ -388,45 +505,645 @@ NOTES
|
|
|
388
505
|
end
|
|
389
506
|
|
|
390
507
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
major, minor, patch, _pre = RBS::VERSION.split(".", 4)
|
|
394
|
-
major = major.to_i
|
|
395
|
-
minor = minor.to_i
|
|
396
|
-
patch = patch.to_i
|
|
508
|
+
# Pull requests with one of these labels are omitted from the changelog.
|
|
509
|
+
CHANGELOG_SKIP_LABELS = ["skip-changelog"]
|
|
397
510
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
511
|
+
# Resolves the commit-ish the changelog starts from.
|
|
512
|
+
#
|
|
513
|
+
# `version` is a version number, a tag, or any commit-ish. When it is omitted, the latest tag
|
|
514
|
+
# matching `tag_glob` is used, skipping the ones matching `exclude_globs`.
|
|
515
|
+
#
|
|
516
|
+
def resolve_changelog_base(version, tag_glob:, exclude_globs: [])
|
|
517
|
+
require "open3"
|
|
518
|
+
|
|
519
|
+
from =
|
|
520
|
+
if version
|
|
521
|
+
# `4.1.0` and `v4.1.0` both mean the tag `v4.1.0`, while `master` or a SHA is used as is.
|
|
522
|
+
version.match?(/\A\d/) ? "v#{version}" : version
|
|
523
|
+
else
|
|
524
|
+
command = ["git", "describe", "--tags", "--match", tag_glob, "--abbrev=0"]
|
|
525
|
+
exclude_globs.each { |glob| command.push("--exclude", glob) }
|
|
526
|
+
|
|
527
|
+
output, status = Open3.capture2(*command)
|
|
528
|
+
raise "🚨 Cannot detect the latest tag matching `#{tag_glob}`. Give the previous version explicitly." unless status.success?
|
|
529
|
+
output.chomp
|
|
530
|
+
end
|
|
403
531
|
|
|
404
|
-
|
|
532
|
+
_, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{from}^{commit}")
|
|
533
|
+
raise "🚨 No such commit-ish: `#{from}`" unless status.success?
|
|
405
534
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
"pr",
|
|
409
|
-
"list",
|
|
410
|
-
"--limit=10000",
|
|
411
|
-
"--json",
|
|
412
|
-
"url,title,number",
|
|
413
|
-
"--search" ,
|
|
414
|
-
"milestone:\"#{milestone}\" is:merged sort:updated-desc -label:Released"
|
|
415
|
-
]
|
|
535
|
+
from
|
|
536
|
+
end
|
|
416
537
|
|
|
538
|
+
# Runs a GraphQL query against the repository of the working directory.
|
|
539
|
+
#
|
|
540
|
+
# `body` is the selection set inside `repository`, so a query can use the `$owner` and `$name`
|
|
541
|
+
# variables. Returns the contents of `data.repository`.
|
|
542
|
+
#
|
|
543
|
+
def changelog_graphql(body)
|
|
417
544
|
require "open3"
|
|
545
|
+
require "json"
|
|
546
|
+
|
|
547
|
+
@changelog_repository ||=
|
|
548
|
+
begin
|
|
549
|
+
output, status = Open3.capture2("gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner")
|
|
550
|
+
raise status.inspect unless status.success?
|
|
551
|
+
output.chomp.split("/", 2)
|
|
552
|
+
end
|
|
553
|
+
owner, name = @changelog_repository
|
|
554
|
+
|
|
555
|
+
query = <<~GRAPHQL
|
|
556
|
+
query($owner: String!, $name: String!) {
|
|
557
|
+
repository(owner: $owner, name: $name) {
|
|
558
|
+
#{body}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
GRAPHQL
|
|
562
|
+
|
|
563
|
+
output, status = Open3.capture2(
|
|
564
|
+
"gh", "api", "graphql",
|
|
565
|
+
"-f", "query=#{query}",
|
|
566
|
+
"-f", "owner=#{owner}",
|
|
567
|
+
"-f", "name=#{name}",
|
|
568
|
+
binmode: true
|
|
569
|
+
)
|
|
570
|
+
raise status.inspect unless status.success?
|
|
571
|
+
|
|
572
|
+
# GitHub always answers in UTF-8, while the default external encoding follows the locale. Without
|
|
573
|
+
# this, a pull request body with an emoji fails to parse under `LANG=C`, as in GitHub Actions.
|
|
574
|
+
JSON.parse(output.force_encoding(Encoding::UTF_8), symbolize_names: true).dig(:data, :repository)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Lists the commits between `from` and `HEAD`, newest first.
|
|
578
|
+
#
|
|
579
|
+
# Giving `paths` limits the commits to the ones touching the paths.
|
|
580
|
+
#
|
|
581
|
+
def changelog_commits(from, paths: [])
|
|
582
|
+
require "open3"
|
|
583
|
+
|
|
584
|
+
command = ["git", "log", "--format=%H", "#{from}..HEAD"]
|
|
585
|
+
# `--simplify-merges` keeps the default history simplification from following only one parent of
|
|
586
|
+
# a merge commit, which can drop the other side. Note that `--full-history` alone is wrong here:
|
|
587
|
+
# it also lists merge commits that do not touch the paths, bringing back the excluded pull
|
|
588
|
+
# requests. The two flags produce the same commits as the default mode for this repository today.
|
|
589
|
+
command.push("--full-history", "--simplify-merges", "--", *paths) unless paths.empty?
|
|
590
|
+
|
|
418
591
|
output, status = Open3.capture2(*command)
|
|
419
592
|
raise status.inspect unless status.success?
|
|
420
593
|
|
|
421
|
-
|
|
422
|
-
|
|
594
|
+
output.lines.map(&:chomp).reject(&:empty?)
|
|
595
|
+
end
|
|
423
596
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
597
|
+
# Finds the pull requests the commits came from, keeping the order of `commits`.
|
|
598
|
+
#
|
|
599
|
+
# Returns the pull requests for the changelog and the ones omitted by `skip_labels`.
|
|
600
|
+
#
|
|
601
|
+
def changelog_pull_requests(commits, skip_labels: CHANGELOG_SKIP_LABELS)
|
|
602
|
+
pull_requests = {}
|
|
603
|
+
skipped = {}
|
|
604
|
+
|
|
605
|
+
commits.each_slice(50) do |slice|
|
|
606
|
+
# Ask GitHub which pull requests the commits came from, so that any merge strategy -- merge
|
|
607
|
+
# commit, squash, or rebase -- is handled without parsing commit messages.
|
|
608
|
+
aliases = slice.map.with_index do |commit, index|
|
|
609
|
+
<<~GRAPHQL
|
|
610
|
+
c#{index}: object(oid: "#{commit}") {
|
|
611
|
+
... on Commit {
|
|
612
|
+
associatedPullRequests(first: 10) {
|
|
613
|
+
nodes {
|
|
614
|
+
number title url merged
|
|
615
|
+
labels(first: 100) { nodes { name } }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
GRAPHQL
|
|
428
621
|
end
|
|
622
|
+
|
|
623
|
+
changelog_graphql(aliases.join("\n")).each_value do |commit|
|
|
624
|
+
next unless commit
|
|
625
|
+
|
|
626
|
+
commit.dig(:associatedPullRequests, :nodes).each do |pr|
|
|
627
|
+
next unless pr[:merged]
|
|
628
|
+
|
|
629
|
+
pr = { number: pr[:number], title: pr[:title], url: pr[:url], labels: pr.dig(:labels, :nodes).map { |label| label[:name] } }
|
|
630
|
+
if (pr[:labels] & skip_labels).empty?
|
|
631
|
+
pull_requests[pr[:number]] ||= pr
|
|
632
|
+
else
|
|
633
|
+
skipped[pr[:number]] ||= pr
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
[pull_requests.values, skipped.values]
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Fetches the details that help classifying the pull requests: the changed files and the body.
|
|
643
|
+
#
|
|
644
|
+
def changelog_pull_request_details(pull_requests)
|
|
645
|
+
pull_requests.each_slice(50).flat_map do |slice|
|
|
646
|
+
aliases = slice.map do |pr|
|
|
647
|
+
<<~GRAPHQL
|
|
648
|
+
p#{pr[:number]}: pullRequest(number: #{pr[:number]}) {
|
|
649
|
+
body
|
|
650
|
+
author { login }
|
|
651
|
+
files(first: 100) {
|
|
652
|
+
nodes { path }
|
|
653
|
+
pageInfo { hasNextPage }
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
GRAPHQL
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
details = changelog_graphql(aliases.join("\n"))
|
|
660
|
+
|
|
661
|
+
slice.map do |pr|
|
|
662
|
+
detail = details[:"p#{pr[:number]}"] or next pr
|
|
663
|
+
|
|
664
|
+
pr.merge(
|
|
665
|
+
author: detail.dig(:author, :login),
|
|
666
|
+
# The body is a hint for writing the changelog, not a copy source. Keep it short.
|
|
667
|
+
body: detail[:body].to_s.strip.slice(0, 1000),
|
|
668
|
+
files: detail.dig(:files, :nodes).map { |file| file[:path] },
|
|
669
|
+
files_truncated: detail.dig(:files, :pageInfo, :hasNextPage)
|
|
670
|
+
)
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Reports the pull requests omitted by their label, so that they do not disappear silently.
|
|
676
|
+
#
|
|
677
|
+
def warn_skipped_pull_requests(skipped, skip_labels)
|
|
678
|
+
return if skipped.empty?
|
|
679
|
+
|
|
680
|
+
numbers = skipped.map { |pr| "##{pr[:number]}" }
|
|
681
|
+
numbers = numbers.take(20).push("and #{numbers.size - 20} more") if numbers.size > 20
|
|
682
|
+
|
|
683
|
+
$stderr.puts
|
|
684
|
+
$stderr.puts " (⏭️ Skipped #{skipped.size} pull request(s) labeled #{skip_labels.map { |label| "`#{label}`" }.join(" or ")}: #{numbers.join(", ")})"
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Prints the changelog template listing the pull requests merged between `from` and `HEAD`.
|
|
688
|
+
#
|
|
689
|
+
# The changelog goes to STDOUT and everything else goes to STDERR, so that the output can be
|
|
690
|
+
# piped to another command: `rake gem:changelog | pbcopy`
|
|
691
|
+
#
|
|
692
|
+
def print_changelog(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
|
|
693
|
+
$stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
|
|
694
|
+
|
|
695
|
+
commits = changelog_commits(from, paths: paths)
|
|
696
|
+
if commits.empty?
|
|
697
|
+
$stderr.puts " (🤔 There is no commit after `#{from}`.)"
|
|
698
|
+
return
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
|
|
702
|
+
|
|
703
|
+
if pull_requests.empty?
|
|
704
|
+
$stderr.puts " (🤔 No pull request is associated to the commits after `#{from}`.)"
|
|
429
705
|
else
|
|
430
|
-
puts
|
|
706
|
+
$stderr.puts
|
|
707
|
+
pull_requests.each do |pr|
|
|
708
|
+
puts "* #{pr[:title]} ([##{pr[:number]}](#{pr[:url]}))"
|
|
709
|
+
end
|
|
710
|
+
$stdout.flush
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
warn_skipped_pull_requests(skipped, skip_labels)
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# Prints the same pull requests as `print_changelog` as JSON, with the details that help
|
|
717
|
+
# classifying them into the sections of CHANGELOG.md.
|
|
718
|
+
#
|
|
719
|
+
# This is the input for the release automation, so it always prints a valid JSON document.
|
|
720
|
+
#
|
|
721
|
+
def print_changelog_json(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
|
|
722
|
+
require "json"
|
|
723
|
+
|
|
724
|
+
$stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
|
|
725
|
+
|
|
726
|
+
commits = changelog_commits(from, paths: paths)
|
|
727
|
+
pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
|
|
728
|
+
pull_requests = changelog_pull_request_details(pull_requests)
|
|
729
|
+
|
|
730
|
+
$stderr.puts " (📋 #{pull_requests.size} pull request(s))"
|
|
731
|
+
|
|
732
|
+
puts JSON.pretty_generate(
|
|
733
|
+
{
|
|
734
|
+
from: from,
|
|
735
|
+
to: "HEAD",
|
|
736
|
+
pull_requests: pull_requests,
|
|
737
|
+
skipped: skipped
|
|
738
|
+
}
|
|
739
|
+
)
|
|
740
|
+
$stdout.flush
|
|
741
|
+
|
|
742
|
+
warn_skipped_pull_requests(skipped, skip_labels)
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
namespace :gem do
|
|
746
|
+
# The gem is developed in the whole repository except the Rust crate, which has its own release
|
|
747
|
+
# cycle. Note that this is an *exclusion*, not a list of the directories shipped in the gem:
|
|
748
|
+
# changes in `test/` or `.github/` are part of the gem's changelog too.
|
|
749
|
+
# A constant defined in a `namespace` block is a top-level constant, so it needs the prefix.
|
|
750
|
+
GEM_CHANGELOG_PATHS = [".", ":(exclude)rust"]
|
|
751
|
+
|
|
752
|
+
# The tags a release proper starts *after*, rather than at.
|
|
753
|
+
GEM_PRERELEASE_TAGS = ["v*.pre*", "v*.dev*"]
|
|
754
|
+
|
|
755
|
+
# Where the changelog of the release being prepared starts, derived from `RBS::VERSION`:
|
|
756
|
+
#
|
|
757
|
+
# * `X.Y.Z.pre.N` documents what changed since `X.Y.Z.pre.N-1`, so it starts from the latest tag.
|
|
758
|
+
# * `X.Y.Z` documents the whole cycle, the prereleases included, so it skips the prerelease tags
|
|
759
|
+
# in between and starts from the previous release proper.
|
|
760
|
+
#
|
|
761
|
+
# This is the step that is easy to get wrong by hand: on a release proper the latest tag is a
|
|
762
|
+
# prerelease, so the obvious default would produce only the tail of the cycle. Passing a version
|
|
763
|
+
# explicitly overrides all of it.
|
|
764
|
+
#
|
|
765
|
+
def changelog_base(version)
|
|
766
|
+
excluded = Gem::Version.new(RBS::VERSION).prerelease? ? [] : GEM_PRERELEASE_TAGS
|
|
767
|
+
resolve_changelog_base(version, tag_glob: "v*", exclude_globs: excluded)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
desc "Generate changelog template from GH pull requests merged since the previous release"
|
|
771
|
+
task :changelog, [:version] do |_task, args|
|
|
772
|
+
print_changelog(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
namespace :changelog do
|
|
776
|
+
desc "Print the pull requests of `gem:changelog` as JSON, with the changed files and body of each"
|
|
777
|
+
task :json, [:version] do |_task, args|
|
|
778
|
+
print_changelog_json(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
desc "Publish the GitHub release for RBS::VERSION, unless it is a `.dev.` version"
|
|
783
|
+
task :gh_release do
|
|
784
|
+
require "open3"
|
|
785
|
+
|
|
786
|
+
version = Gem::Version.new(RBS::VERSION)
|
|
787
|
+
major, minor, *_ = RBS::VERSION.split(".")
|
|
788
|
+
tag = "v#{RBS::VERSION}"
|
|
789
|
+
|
|
790
|
+
# There are three kinds of release: `X.Y.Z`, `X.Y.Z.pre.N`, and `X.Y.Z.dev.N`.
|
|
791
|
+
# The `.dev.N` ones are cut from the development line for people who need a
|
|
792
|
+
# specific change early; they are not written up in the changelog, so there are
|
|
793
|
+
# no notes to publish and nothing worth announcing.
|
|
794
|
+
if version.segments.include?("dev")
|
|
795
|
+
puts "⏭️ #{RBS::VERSION} is a dev release, so there is no GitHub release to publish."
|
|
796
|
+
next
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# The release is created against an existing tag, so that the artifacts and the
|
|
800
|
+
# notes describe a commit that is already immutable.
|
|
801
|
+
_, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{tag}^{commit}")
|
|
802
|
+
raise "🚨 No such tag: `#{tag}`. Tag the release before creating the GitHub release." unless status.success?
|
|
803
|
+
|
|
804
|
+
# The topmost section of the changelog is this release, minus its own heading.
|
|
805
|
+
# The encoding is explicit because the default external encoding follows the
|
|
806
|
+
# locale, and the changelog is not ASCII.
|
|
807
|
+
content = File.read(File.join(__dir__, "CHANGELOG.md"), encoding: Encoding::UTF_8)
|
|
808
|
+
section = content.scan(/^## \d.*?(?=^## \d)/m)[0] or raise "🚨 Cannot find a release section in CHANGELOG.md"
|
|
809
|
+
heading, _, body = section.partition("\n")
|
|
810
|
+
heading.include?(RBS::VERSION) or raise "🚨 CHANGELOG.md starts with `#{heading.strip}`, which is not #{RBS::VERSION}"
|
|
811
|
+
|
|
812
|
+
notes = <<~NOTES
|
|
813
|
+
[Release note](https://github.com/ruby/rbs/wiki/Release-Note-#{major}.#{minor})
|
|
814
|
+
|
|
815
|
+
#{body.strip}
|
|
816
|
+
NOTES
|
|
817
|
+
|
|
818
|
+
# Published rather than drafted: the notes are the changelog section that was
|
|
819
|
+
# already reviewed in the release pull request, so there is nothing left to edit.
|
|
820
|
+
command = [
|
|
821
|
+
"gh", "release", "create", tag,
|
|
822
|
+
"--title=#{RBS::VERSION}",
|
|
823
|
+
"--notes=#{notes}"
|
|
824
|
+
]
|
|
825
|
+
command << "--prerelease" if version.prerelease?
|
|
826
|
+
|
|
827
|
+
output, status = Open3.capture2(*command)
|
|
828
|
+
raise "🚨 `gh release create` failed: #{status.inspect}" unless status.success?
|
|
829
|
+
|
|
830
|
+
puts "📝 Released #{tag}: #{output.chomp}"
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
desc "Compile extension without C23 extensions"
|
|
835
|
+
task :compile_c99 do
|
|
836
|
+
ENV["TEST_NO_C23"] = "true"
|
|
837
|
+
Rake::Task[:"compile"].invoke
|
|
838
|
+
ensure
|
|
839
|
+
ENV.delete("TEST_NO_C23")
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
task :prepare_bench do
|
|
843
|
+
ENV.delete("DEBUG")
|
|
844
|
+
Rake::Task[:"clobber"].invoke
|
|
845
|
+
Rake::Task[:"templates"].invoke
|
|
846
|
+
Rake::Task[:"compile"].invoke
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
task :prepare_profiling do
|
|
850
|
+
ENV["DEBUG"] = "1"
|
|
851
|
+
Rake::Task[:"clobber"].invoke
|
|
852
|
+
Rake::Task[:"templates"].invoke
|
|
853
|
+
Rake::Task[:"compile"].invoke
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
namespace :wasm do
|
|
857
|
+
WASM_DIR = File.expand_path("wasm", __dir__)
|
|
858
|
+
WASM_OUTPUT = File.join(WASM_DIR, "rbs_parser.wasm")
|
|
859
|
+
|
|
860
|
+
# The parser under src/ is plain, self-contained C with no dependency on the
|
|
861
|
+
# Ruby C API, so it can be compiled to WebAssembly as-is. The only extra
|
|
862
|
+
# translation unit is the entry-point shim under wasm/.
|
|
863
|
+
def wasm_source_files
|
|
864
|
+
Dir.glob(File.join(__dir__, "src/**/*.c")).sort + [File.join(WASM_DIR, "rbs_wasm.c")]
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
# Locate the clang shipped with the WASI SDK.
|
|
868
|
+
#
|
|
869
|
+
# The system clang can target wasm32, but the WASI SDK additionally provides
|
|
870
|
+
# the wasi-libc sysroot and the wasm32 compiler-rt builtins that the link
|
|
871
|
+
# step needs, so we require it explicitly.
|
|
872
|
+
def wasi_clang
|
|
873
|
+
sdk = ENV["WASI_SDK_PATH"]
|
|
874
|
+
if sdk.nil? || sdk.empty?
|
|
875
|
+
raise <<~MSG
|
|
876
|
+
WASI_SDK_PATH is not set.
|
|
877
|
+
|
|
878
|
+
Install the WASI SDK from https://github.com/WebAssembly/wasi-sdk/releases
|
|
879
|
+
and point WASI_SDK_PATH at the extracted directory, for example:
|
|
880
|
+
|
|
881
|
+
export WASI_SDK_PATH=/opt/wasi-sdk
|
|
882
|
+
rake wasm:build
|
|
883
|
+
MSG
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
clang = File.join(sdk, "bin", "clang")
|
|
887
|
+
raise "clang not found at #{clang} (is WASI_SDK_PATH correct?)" unless File.executable?(clang)
|
|
888
|
+
|
|
889
|
+
clang
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
desc "Build the RBS parser as a WebAssembly module (requires WASI_SDK_PATH)"
|
|
893
|
+
task :build do
|
|
894
|
+
mkdir_p WASM_DIR
|
|
895
|
+
sh wasi_clang,
|
|
896
|
+
"--target=wasm32-wasip1",
|
|
897
|
+
# No `main`; the host calls `_initialize` and then the exported functions.
|
|
898
|
+
"-mexec-model=reactor",
|
|
899
|
+
"-std=gnu11",
|
|
900
|
+
"-O2",
|
|
901
|
+
"-Wno-unused-parameter",
|
|
902
|
+
"-I#{File.join(__dir__, "include")}",
|
|
903
|
+
"-o", WASM_OUTPUT,
|
|
904
|
+
*wasm_source_files
|
|
905
|
+
puts "Built #{WASM_OUTPUT}"
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
desc "Build and smoke-test the WebAssembly module (requires wasmtime)"
|
|
909
|
+
task :check => :build do
|
|
910
|
+
wasmtime = ENV["WASMTIME"] || "wasmtime"
|
|
911
|
+
|
|
912
|
+
# `rbs_wasm_selftest` parses a small fixed signature and returns 1 on
|
|
913
|
+
# success. `--invoke` prints the return value to stdout.
|
|
914
|
+
output = IO.popen([wasmtime, "run", "--invoke", "rbs_wasm_selftest", WASM_OUTPUT], err: File::NULL, &:read).to_s.strip
|
|
915
|
+
|
|
916
|
+
if output == "1"
|
|
917
|
+
puts "WebAssembly selftest passed."
|
|
918
|
+
else
|
|
919
|
+
raise "WebAssembly selftest failed: rbs_wasm_selftest returned #{output.inspect} (expected \"1\")"
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
# Where the runtime looks for the module by default (see RBS::WASM::Runtime).
|
|
924
|
+
JRUBY_WASM_DIR = File.expand_path("lib/rbs/wasm", __dir__)
|
|
925
|
+
|
|
926
|
+
desc "Download the Chicory/ASM jars into the local Maven repository (~/.m2). Run on JRuby."
|
|
927
|
+
task :install_jars do
|
|
928
|
+
# Resolves the `jar` requirements from rbs.gemspec via Maven and downloads
|
|
929
|
+
# them (and their transitive deps) into ~/.m2, the same way `gem install`
|
|
930
|
+
# does; the jars are not copied into the gem. The platform is forced to java
|
|
931
|
+
# because Jars::Installer skips non-java gems, and write_require_file is false
|
|
932
|
+
# because lib/rbs_jars.rb is hand-maintained (the generator mangles the
|
|
933
|
+
# `com.dylibso.chicory:runtime` artifact id).
|
|
934
|
+
require "jars/installer"
|
|
935
|
+
spec = Gem::Specification.load("rbs.gemspec")
|
|
936
|
+
spec.platform = "java"
|
|
937
|
+
Jars::Installer.new(spec).install_jars(write_require_file: false)
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
desc "Build rbs_parser.wasm and copy it next to RBS::WASM::Runtime"
|
|
941
|
+
task :jruby_setup => [:build] do
|
|
942
|
+
cp WASM_OUTPUT, File.join(JRUBY_WASM_DIR, "rbs_parser.wasm")
|
|
943
|
+
puts "rbs_parser.wasm is ready under #{JRUBY_WASM_DIR}"
|
|
944
|
+
end
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
namespace :rust do
|
|
948
|
+
namespace :rbs do
|
|
949
|
+
RUST_DIR = File.expand_path("rust", __dir__)
|
|
950
|
+
RBS_VERSION_FILE = File.join(RUST_DIR, "rbs_version")
|
|
951
|
+
|
|
952
|
+
VENDOR_TARGETS = {
|
|
953
|
+
"ruby-rbs-sys" => %w[include src],
|
|
954
|
+
"ruby-rbs" => %w[config.yml],
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
desc "Sync vendored RBS source from the pinned version"
|
|
958
|
+
task :sync do
|
|
959
|
+
unless File.exist?(RBS_VERSION_FILE)
|
|
960
|
+
raise "#{RBS_VERSION_FILE} not found. Run `rake rust:rbs:pin[VERSION]` first."
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
version = File.read(RBS_VERSION_FILE).strip
|
|
964
|
+
raise "#{RBS_VERSION_FILE} is empty" if version.empty?
|
|
965
|
+
|
|
966
|
+
puts "Syncing vendor/rbs/ from #{version}..."
|
|
967
|
+
|
|
968
|
+
VENDOR_TARGETS.each do |crate, entries|
|
|
969
|
+
vendor_dir = File.join(RUST_DIR, crate, "vendor", "rbs")
|
|
970
|
+
|
|
971
|
+
puts " Copying files for #{crate}:"
|
|
972
|
+
chmod_R "u+w", vendor_dir, verbose: false if File.exist?(vendor_dir)
|
|
973
|
+
rm_rf vendor_dir, verbose: false
|
|
974
|
+
mkdir_p vendor_dir, verbose: false
|
|
975
|
+
|
|
976
|
+
entries.each do |entry|
|
|
977
|
+
target = File.join(vendor_dir, entry)
|
|
978
|
+
|
|
979
|
+
# Extract the entry from the pinned git tag using git archive
|
|
980
|
+
IO.popen(["git", "archive", "--format=tar", version, "--", entry], "rb") do |tar|
|
|
981
|
+
IO.popen(["tar", "xf", "-", "-C", vendor_dir], "wb") do |extract|
|
|
982
|
+
IO.copy_stream(tar, extract)
|
|
983
|
+
end
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
raise "Failed to extract #{entry} from #{version}" unless File.exist?(target)
|
|
987
|
+
puts " #{entry}"
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
# Make files read-only to prevent accidental edits
|
|
991
|
+
chmod_R "a-w", vendor_dir, verbose: false
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
puts "📦 Synced vendor/rbs/ from #{version} (read-only)"
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
desc "Pin a specific RBS version for Rust crates (e.g., rake rust:rbs:pin[v4.0.3])"
|
|
998
|
+
task :pin, [:version] do |_t, args|
|
|
999
|
+
version = args[:version] or raise "Usage: rake rust:rbs:pin[VERSION]"
|
|
1000
|
+
|
|
1001
|
+
# Verify the tag exists
|
|
1002
|
+
unless system("git", "rev-parse", "--verify", "#{version}^{commit}", out: File::NULL, err: File::NULL)
|
|
1003
|
+
raise "Tag #{version} not found"
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
File.write(RBS_VERSION_FILE, "#{version}\n")
|
|
1007
|
+
puts "📌 Pinned RBS version to #{version}"
|
|
1008
|
+
|
|
1009
|
+
Rake::Task["rust:rbs:sync"].invoke
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
desc "Create symlinks from vendor/rbs/ to the repository root (for development/CI)"
|
|
1013
|
+
task :symlink do
|
|
1014
|
+
VENDOR_TARGETS.each do |crate, entries|
|
|
1015
|
+
vendor_dir = File.join(RUST_DIR, crate, "vendor", "rbs")
|
|
1016
|
+
|
|
1017
|
+
puts "Setting up symlinks for #{crate}..."
|
|
1018
|
+
entries.each do |entry|
|
|
1019
|
+
puts " #{entry} -> repository root"
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
chmod_R "u+w", vendor_dir, verbose: false if File.exist?(vendor_dir)
|
|
1023
|
+
rm_rf vendor_dir, verbose: false
|
|
1024
|
+
mkdir_p vendor_dir, verbose: false
|
|
1025
|
+
|
|
1026
|
+
entries.each do |entry|
|
|
1027
|
+
ln_s File.join("..", "..", "..", "..", entry), File.join(vendor_dir, entry), verbose: false
|
|
1028
|
+
end
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
puts "🔗 Symlinked vendor/rbs/ to repository root"
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
namespace :publish do
|
|
1036
|
+
def self.prepare_publish_branch(crate_name)
|
|
1037
|
+
dry_run = ENV["RBS_RUST_PUBLISH_DRY_RUN"]
|
|
1038
|
+
|
|
1039
|
+
version_file = File.join(RUST_DIR, "rbs_version")
|
|
1040
|
+
|
|
1041
|
+
unless File.exist?(version_file)
|
|
1042
|
+
raise "#{version_file} not found. Run `rake rust:rbs:pin[VERSION]` first."
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
rbs_version = File.read(version_file).strip
|
|
1046
|
+
raise "#{version_file} is empty" if rbs_version.empty?
|
|
1047
|
+
|
|
1048
|
+
crate_version = File.read(File.join(RUST_DIR, crate_name, "Cargo.toml"))[/^version\s*=\s*"(.+)"/, 1]
|
|
1049
|
+
release_branch = "rust/release-#{crate_name}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
|
1050
|
+
|
|
1051
|
+
puts "=" * 60
|
|
1052
|
+
puts "Rust crate publish: #{crate_name}#{dry_run ? " (DRY RUN)" : ""}"
|
|
1053
|
+
puts "=" * 60
|
|
1054
|
+
puts " RBS source version: #{rbs_version}"
|
|
1055
|
+
puts " #{crate_name}: #{crate_version} (tag: #{crate_name}-v#{crate_version})"
|
|
1056
|
+
puts " Release branch: #{release_branch}"
|
|
1057
|
+
puts "=" * 60
|
|
1058
|
+
|
|
1059
|
+
# Check that vendor dirs contain real files, not symlinks
|
|
1060
|
+
entries = VENDOR_TARGETS.fetch(crate_name)
|
|
1061
|
+
entries.each do |entry|
|
|
1062
|
+
path = File.join(RUST_DIR, crate_name, "vendor", "rbs", entry)
|
|
1063
|
+
if File.symlink?(path)
|
|
1064
|
+
raise "#{path} is a symlink. Run `rake rust:rbs:sync` first."
|
|
1065
|
+
end
|
|
1066
|
+
unless File.exist?(path)
|
|
1067
|
+
raise "#{path} does not exist. Run `rake rust:rbs:sync` first."
|
|
1068
|
+
end
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
# Ensure working tree is clean before publishing
|
|
1072
|
+
unless `git status --porcelain`.strip.empty?
|
|
1073
|
+
raise "💢 Working tree is dirty. Please commit or stash your changes before publishing."
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
# Create a release branch with vendor files committed
|
|
1077
|
+
original_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
1078
|
+
|
|
1079
|
+
sh "git", "checkout", "-b", release_branch, verbose: false
|
|
1080
|
+
vendor_path = File.join("rust", crate_name, "vendor", "rbs")
|
|
1081
|
+
sh "git", "add", "-f", vendor_path, verbose: false
|
|
1082
|
+
sh "git", "commit", "-m", "Publish #{crate_name} (RBS #{rbs_version})", verbose: false
|
|
1083
|
+
|
|
1084
|
+
[dry_run, crate_version, original_branch]
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
desc "Publish ruby-rbs-sys crate to crates.io (set RBS_RUST_PUBLISH_DRY_RUN=1 for dry-run only)"
|
|
1088
|
+
task :"ruby-rbs-sys" do
|
|
1089
|
+
crate_name = "ruby-rbs-sys"
|
|
1090
|
+
dry_run, crate_version, original_branch = prepare_publish_branch(crate_name)
|
|
1091
|
+
|
|
1092
|
+
begin
|
|
1093
|
+
puts "🔰 Dry-run publishing..."
|
|
1094
|
+
|
|
1095
|
+
Dir.chdir(File.join(RUST_DIR, crate_name)) do
|
|
1096
|
+
sh "cargo", "publish", "--dry-run"
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
puts "✅ Dry-run succeeded!"
|
|
1100
|
+
|
|
1101
|
+
unless dry_run
|
|
1102
|
+
puts "💪 Publishing #{crate_name} for real..."
|
|
1103
|
+
|
|
1104
|
+
Dir.chdir(File.join(RUST_DIR, crate_name)) do
|
|
1105
|
+
sh "cargo", "publish"
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
sh "git", "tag", "#{crate_name}-v#{crate_version}"
|
|
1109
|
+
sh "git", "push", "origin", "#{crate_name}-v#{crate_version}"
|
|
1110
|
+
|
|
1111
|
+
puts "🎉 Published #{crate_name} successfully!"
|
|
1112
|
+
end
|
|
1113
|
+
ensure
|
|
1114
|
+
sh "git", "checkout", original_branch, verbose: false
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
|
|
1118
|
+
desc "Publish ruby-rbs crate to crates.io (set RBS_RUST_PUBLISH_DRY_RUN=1 for dry-run only)"
|
|
1119
|
+
task :"ruby-rbs" do
|
|
1120
|
+
crate_name = "ruby-rbs"
|
|
1121
|
+
dry_run, crate_version, original_branch = prepare_publish_branch(crate_name)
|
|
1122
|
+
|
|
1123
|
+
begin
|
|
1124
|
+
puts "🔰 Dry-run publishing..."
|
|
1125
|
+
|
|
1126
|
+
Dir.chdir(File.join(RUST_DIR, crate_name)) do
|
|
1127
|
+
sh "cargo", "publish", "--dry-run", "--no-verify"
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
puts "✅ Dry-run succeeded!"
|
|
1131
|
+
|
|
1132
|
+
unless dry_run
|
|
1133
|
+
puts "💪 Publishing #{crate_name} for real..."
|
|
1134
|
+
|
|
1135
|
+
Dir.chdir(File.join(RUST_DIR, crate_name)) do
|
|
1136
|
+
sh "cargo", "publish"
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
sh "git", "tag", "#{crate_name}-v#{crate_version}"
|
|
1140
|
+
sh "git", "push", "origin", "#{crate_name}-v#{crate_version}"
|
|
1141
|
+
|
|
1142
|
+
puts "🎉 Published #{crate_name} successfully!"
|
|
1143
|
+
end
|
|
1144
|
+
ensure
|
|
1145
|
+
sh "git", "checkout", original_branch, verbose: false
|
|
1146
|
+
end
|
|
1147
|
+
end
|
|
431
1148
|
end
|
|
432
1149
|
end
|