makiri 0.7.0-x64-mingw-ucrt

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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +74 -0
  3. data/.github/workflows/conformance.yml +116 -0
  4. data/.github/workflows/libfuzzer.yml +83 -0
  5. data/.github/workflows/release.yml +249 -0
  6. data/.github/workflows/security.yml +180 -0
  7. data/.github/workflows/valgrind.yml +138 -0
  8. data/.github/workflows/verify.yml +37 -0
  9. data/.gitmodules +3 -0
  10. data/CHANGELOG.md +425 -0
  11. data/LICENSE +176 -0
  12. data/NOTICE +12 -0
  13. data/README.md +334 -0
  14. data/Rakefile +464 -0
  15. data/lib/makiri/3.2/makiri.so +0 -0
  16. data/lib/makiri/3.3/makiri.so +0 -0
  17. data/lib/makiri/3.4/makiri.so +0 -0
  18. data/lib/makiri/4.0/makiri.so +0 -0
  19. data/lib/makiri/attr.rb +14 -0
  20. data/lib/makiri/cdata_section.rb +19 -0
  21. data/lib/makiri/comment.rb +14 -0
  22. data/lib/makiri/compat_aliases.rb +19 -0
  23. data/lib/makiri/css.rb +11 -0
  24. data/lib/makiri/document.rb +18 -0
  25. data/lib/makiri/document_fragment.rb +26 -0
  26. data/lib/makiri/document_type.rb +14 -0
  27. data/lib/makiri/element.rb +17 -0
  28. data/lib/makiri/html/document.rb +105 -0
  29. data/lib/makiri/html/node_methods.rb +18 -0
  30. data/lib/makiri/html.rb +12 -0
  31. data/lib/makiri/node.rb +264 -0
  32. data/lib/makiri/node_set.rb +118 -0
  33. data/lib/makiri/processing_instruction.rb +26 -0
  34. data/lib/makiri/text.rb +16 -0
  35. data/lib/makiri/version.rb +5 -0
  36. data/lib/makiri/xml/builder.rb +271 -0
  37. data/lib/makiri/xml/document.rb +24 -0
  38. data/lib/makiri/xml/node_methods.rb +84 -0
  39. data/lib/makiri/xml.rb +10 -0
  40. data/lib/makiri/xpath.rb +14 -0
  41. data/lib/makiri/xpath_context.rb +49 -0
  42. data/lib/makiri.rb +66 -0
  43. data/script/build_native_gem.rb +50 -0
  44. data/script/check_alloc_failures.rb +266 -0
  45. data/script/check_c_safety.rb +324 -0
  46. data/script/check_c_safety_allowlist.yml +108 -0
  47. data/script/check_leaks.rb +64 -0
  48. data/script/leaks_harness.rb +71 -0
  49. data/sig/makiri.rbs +4 -0
  50. data/suppressions/ruby.supp +162 -0
  51. data/verify/Makefile +237 -0
  52. data/verify/cbmc_models.c +38 -0
  53. data/verify/harness_alloc.c +257 -0
  54. data/verify/harness_buf.c +167 -0
  55. data/verify/harness_hash.c +28 -0
  56. data/verify/harness_span.c +213 -0
  57. data/verify/harness_spanbuf.c +98 -0
  58. data/verify/harness_utf8.c +70 -0
  59. data/verify/harness_utf8_chain.c +64 -0
  60. data/verify/harness_utf8_words.c +54 -0
  61. data/verify/harness_xml_chars.c +59 -0
  62. data/verify/harness_xml_expand.c +72 -0
  63. data/verify/harness_xml_parse.c +42 -0
  64. data/verify/harness_xpath_lex.c +51 -0
  65. data/verify/harness_xpath_number.c +68 -0
  66. data/verify/harness_xpath_parse.c +57 -0
  67. data/verify/selftest_main.c +16 -0
  68. data/verify/stub.c +10 -0
  69. data/verify/verify.h +54 -0
  70. metadata +161 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2eb7b21bb34a15cc0263881ecf90715fac3263521b58dfd27b5d0ca966738402
4
+ data.tar.gz: 6a570d13671d4ecb8e85a235800a4c9ddf22e63d34c92cebe50c51d89551320c
5
+ SHA512:
6
+ metadata.gz: c552c96226c3a75ad96f9d23a68021c2a4b4c562060ac1fe8392ebd4274fa5216814de1e63ee96149060d85e2293f808b1709e0717a9ca87b004216f91bca053
7
+ data.tar.gz: 7fe9bcdf19dcbb900d8ddf87712ca7f3bc74e532eda2cc5594d267da690eafd9f4aae0cfb62fec75021496c35b4cc01500ba6b991d8d86a1638aab7f106f544e
@@ -0,0 +1,74 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
17
+
18
+ steps:
19
+ - name: Checkout (with vendored Lexbor submodule)
20
+ uses: actions/checkout@v6
21
+ with:
22
+ submodules: recursive
23
+
24
+ - name: Ensure cmake is available
25
+ uses: lukka/get-cmake@latest
26
+
27
+ - name: Set up Ruby
28
+ uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+ bundler-cache: true
32
+
33
+ - name: Compile the extension (builds vendored Lexbor, then the ext)
34
+ run: bundle exec rake compile
35
+
36
+ # The :threading suite is heavy (GC.stress across threads) and checks a
37
+ # structural property that does not vary by OS/Ruby, so run it on a single
38
+ # representative job; the rest of the matrix skips it for a fast run.
39
+ - name: Run the test suite
40
+ run: bundle exec rake spec
41
+ env:
42
+ THREADING: ${{ (matrix.os == 'ubuntu-latest' && matrix.ruby == '3.4') && '1' || '' }}
43
+
44
+ - name: Smoke-load the gem
45
+ shell: bash
46
+ run: bundle exec ruby -Ilib -r makiri -e 'p Makiri::VERSION'
47
+
48
+ sanitize:
49
+ name: AddressSanitizer + UBSan (Ruby ${{ matrix.ruby }})
50
+ runs-on: ubuntu-latest
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ ruby: ["3.4", "4.0"]
55
+ steps:
56
+ - name: Checkout (with vendored Lexbor submodule)
57
+ uses: actions/checkout@v6
58
+ with:
59
+ submodules: recursive
60
+
61
+ - name: Ensure cmake is available
62
+ uses: lukka/get-cmake@latest
63
+
64
+ - name: Set up Ruby
65
+ uses: ruby/setup-ruby@v1
66
+ with:
67
+ ruby-version: ${{ matrix.ruby }}
68
+ bundler-cache: true
69
+
70
+ # Builds the ext with -fsanitize=address,undefined and runs the whole
71
+ # spec suite with the ASan runtime preloaded. Any heap/UB error aborts
72
+ # the run with a non-zero exit, failing the job.
73
+ - name: Build + test under sanitizers
74
+ run: bundle exec rake sanitize
@@ -0,0 +1,116 @@
1
+ name: Conformance
2
+
3
+ # Differential / spec-conformance suites. These compare Makiri against external
4
+ # references (Nokogiri's libxml2/Gumbo XPath+CSS, the html5lib-tests corpus) and
5
+ # catch semantic divergences that the unit specs cannot. The XPath and CSS
6
+ # differentials run OUTSIDE the bundle (the rake tasks use
7
+ # Bundler.with_unbundled_env), so Nokogiri — a bench-only dependency — must be
8
+ # present as a system gem; each job installs it explicitly.
9
+
10
+ on:
11
+ push:
12
+ branches: [main, master]
13
+ pull_request:
14
+ schedule:
15
+ - cron: "37 5 * * *" # nightly, offset from the other workflows
16
+ workflow_dispatch:
17
+
18
+ jobs:
19
+ # PR + push: HTML5 parsing conformance, plus the XPath and CSS differentials
20
+ # over the curated corpus and a moderate batch of generated XPath expressions.
21
+ conformance:
22
+ name: HTML5 + XPath/CSS differential vs Nokogiri
23
+ runs-on: ubuntu-latest
24
+ if: github.event_name != 'schedule'
25
+ steps:
26
+ - name: Checkout (with vendored Lexbor submodule)
27
+ uses: actions/checkout@v6
28
+ with:
29
+ submodules: recursive
30
+
31
+ - name: Ensure cmake is available
32
+ uses: lukka/get-cmake@latest
33
+
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: "3.4"
38
+ bundler-cache: true
39
+
40
+ - name: Compile the extension (builds vendored Lexbor, then the ext)
41
+ run: bundle exec rake compile
42
+
43
+ - name: Install Nokogiri (system gem; the differentials run outside the bundle)
44
+ run: gem install --no-document nokogiri
45
+
46
+ - name: HTML5 parsing conformance (html5lib-tests)
47
+ run: bundle exec rake conformance:html5
48
+
49
+ # XPATH_ARGS must be a real env var, not a rake CLI arg: the task reads
50
+ # ENV['XPATH_ARGS'] inside Bundler.with_unbundled_env, which reverts to the
51
+ # pre-rake environment, so a rake-set var would be dropped.
52
+ - name: XPath 1.0 differential vs Nokogiri (corpus + generated)
53
+ run: bundle exec rake conformance:xpath
54
+ env:
55
+ XPATH_ARGS: "--generate 5000 --seed 1"
56
+
57
+ - name: CSS Selectors differential vs Nokogiri::HTML5
58
+ run: bundle exec rake conformance:css
59
+
60
+ - name: XML XPath 1.0 differential vs Nokogiri::XML
61
+ run: bundle exec rake conformance:xpath_xml
62
+
63
+ - name: XML CSS-selector differential vs Nokogiri::XML
64
+ run: bundle exec rake conformance:css_xml
65
+
66
+ - name: XML Builder differential vs Nokogiri::XML::Builder
67
+ run: bundle exec rake conformance:builder
68
+
69
+ # Nightly: a wide XPath differential sweep across many seeds and a high
70
+ # generated volume, to surface divergences the curated corpus and a single
71
+ # seed miss. Fails fast (bash -e) on the first real divergence.
72
+ conformance-nightly:
73
+ name: Nightly XPath differential sweep
74
+ runs-on: ubuntu-latest
75
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
76
+ steps:
77
+ - name: Checkout (with vendored Lexbor submodule)
78
+ uses: actions/checkout@v6
79
+ with:
80
+ submodules: recursive
81
+
82
+ - name: Ensure cmake is available
83
+ uses: lukka/get-cmake@latest
84
+
85
+ - name: Set up Ruby
86
+ uses: ruby/setup-ruby@v1
87
+ with:
88
+ ruby-version: "3.4"
89
+ bundler-cache: true
90
+
91
+ - name: Compile the extension
92
+ run: bundle exec rake compile
93
+
94
+ - name: Install Nokogiri (system gem)
95
+ run: gem install --no-document nokogiri
96
+
97
+ - name: XPath differential sweep (many seeds, high volume)
98
+ run: |
99
+ for seed in 1 2 3 4 5 6 7 8; do
100
+ echo "::group::seed ${seed}"
101
+ XPATH_ARGS="--generate 20000 --seed ${seed}" bundle exec rake conformance:xpath
102
+ echo "::endgroup::"
103
+ done
104
+
105
+ # The W3C XML conformance suite fetches its (pinned) test corpus on first
106
+ # run, so it lives in the nightly rather than on every PR.
107
+ - name: XML 1.0 well-formedness vs the W3C XML Conformance Test Suite
108
+ run: bundle exec rake conformance:xmlconf
109
+
110
+ # Property-based tree differential: generated documents, Makiri's parsed
111
+ # tree + canonical output vs Nokogiri::XML. Scalable volume - nightly gets
112
+ # a much larger batch than the resident in-suite PBT specs.
113
+ - name: XML property-based tree differential vs Nokogiri::XML
114
+ run: bundle exec rake conformance:xml_pbt
115
+ env:
116
+ PBT_ARGS: "--count 20000"
@@ -0,0 +1,83 @@
1
+ name: libFuzzer
2
+
3
+ on:
4
+ # Nightly: libFuzzer runs are coverage-guided and long-running; they complement
5
+ # the PR-level short fuzz (30s) and the nightly sanitizer fuzz (300s per target).
6
+ # We run them on a schedule because the coverage signal needs sustained CPU
7
+ # time to reach deep branches (e.g. DOCTYPE quote/bracket state machine,
8
+ # reference expansion boundaries).
9
+ schedule:
10
+ - cron: "0 3 * * *"
11
+ workflow_dispatch:
12
+
13
+ jobs:
14
+ # Build the libFuzzer harnesses under clang with -fsanitize=fuzzer,address
15
+ # and run each target for 300s. The corpus is stored as a build artifact so
16
+ # it can be seeded in subsequent runs (regression asset per the roadmap).
17
+ libfuzzer-nightly:
18
+ name: libFuzzer ${{ matrix.target }} (clang)
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 360
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ target: [xml, xpath, xml_xpath]
25
+
26
+ steps:
27
+ - name: Checkout (with vendored Lexbor submodule)
28
+ uses: actions/checkout@v6
29
+ with:
30
+ submodules: recursive
31
+
32
+ - name: Ensure cmake is available
33
+ uses: lukka/get-cmake@latest
34
+
35
+ - name: Set up Ruby
36
+ uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: "3.4"
39
+ bundler-cache: true
40
+
41
+ # Build the vendored Lexbor first (plain mode is fine; the harness links
42
+ # the static archive). The fuzzer binary itself is built with ASan.
43
+ - name: Compile the extension (builds Lexbor)
44
+ run: bundle exec rake compile
45
+
46
+ - name: Install clang
47
+ run: |
48
+ sudo apt-get update
49
+ sudo apt-get install -y clang
50
+
51
+ - name: Build libFuzzer harnesses
52
+ run: |
53
+ cd ext/makiri/fuzz
54
+ make clean
55
+ make all
56
+
57
+ # Restore the previous corpus so the fuzzer starts from the accumulated
58
+ # regression seeds rather than from scratch.
59
+ - name: Restore corpus cache
60
+ uses: actions/cache@v4
61
+ with:
62
+ path: ext/makiri/fuzz/corpus/${{ matrix.target }}
63
+ key: libfuzzer-corpus-${{ matrix.target }}-${{ github.run_id }}
64
+ restore-keys: |
65
+ libfuzzer-corpus-${{ matrix.target }}-
66
+
67
+ - name: Run libFuzzer ${{ matrix.target }} (300s)
68
+ run: |
69
+ mkdir -p ext/makiri/fuzz/corpus/${{ matrix.target }}
70
+ cd ext/makiri/fuzz
71
+ ./${{ matrix.target }}_fuzz \
72
+ -max_total_time=300 \
73
+ -max_len=4096 \
74
+ -print_final_stats=1 \
75
+ corpus/${{ matrix.target }}
76
+
77
+ # Save the mutated corpus as an artifact for download / seeding.
78
+ - name: Upload corpus artifact
79
+ uses: actions/upload-artifact@v4
80
+ with:
81
+ name: libfuzzer-corpus-${{ matrix.target }}
82
+ path: ext/makiri/fuzz/corpus/${{ matrix.target }}
83
+ retention-days: 7
@@ -0,0 +1,249 @@
1
+ name: Release
2
+
3
+ # Build the source gem and precompiled ("fat") native gems for the major
4
+ # platforms, attach them to a GitHub Release, and (optionally, on manual
5
+ # dispatch) publish to RubyGems.
6
+ #
7
+ # Native gems are built on each target platform's own runner — NOT
8
+ # cross-compiled — because the extension builds vendored Lexbor with CMake using
9
+ # the host toolchain (see ext/makiri/extconf.rb). Each runner compiles for
10
+ # Ruby 3.2/3.3/3.4; script/build_native_gem.rb then assembles one gem per
11
+ # platform containing all three ABIs (lib/makiri.rb selects the right one at
12
+ # require time). `gem install` of a native gem does not recompile or need
13
+ # cmake / the Lexbor submodule.
14
+
15
+ on:
16
+ push:
17
+ tags: ["v*"]
18
+ workflow_dispatch:
19
+ inputs:
20
+ publish_to_rubygems:
21
+ description: "Push the built gems to RubyGems (via Trusted Publishing / OIDC)"
22
+ type: boolean
23
+ default: false
24
+
25
+ permissions:
26
+ contents: write # create the GitHub Release and upload assets
27
+
28
+ env:
29
+ RUBY_ABIS: "3.2 3.3 3.4 4.0" # Ruby minor versions a native gem serves
30
+
31
+ jobs:
32
+ # --- source gem (always builds; compiles from source at install time) -------
33
+ source-gem:
34
+ name: Source gem
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v6
38
+ with:
39
+ submodules: recursive # vendored Lexbor must be in the gem's file list
40
+ - uses: ruby/setup-ruby@v1
41
+ with:
42
+ ruby-version: "3.4"
43
+ - name: Build source gem
44
+ run: |
45
+ mkdir -p pkg
46
+ gem build makiri.gemspec -o pkg/makiri-source.gem
47
+ - uses: actions/upload-artifact@v7
48
+ with:
49
+ name: gem-source
50
+ path: pkg/*.gem
51
+ if-no-files-found: error
52
+
53
+ # --- compile the extension natively, per platform x Ruby --------------------
54
+ compile:
55
+ name: Compile ${{ matrix.platform }} (Ruby ${{ matrix.ruby }})
56
+ runs-on: ${{ matrix.os }}
57
+ strategy:
58
+ fail-fast: false
59
+ matrix:
60
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
61
+ platform:
62
+ - x86_64-linux
63
+ - aarch64-linux
64
+ - arm64-darwin
65
+ - x64-mingw-ucrt
66
+ include:
67
+ - { platform: x86_64-linux, os: ubuntu-latest }
68
+ - { platform: aarch64-linux, os: ubuntu-24.04-arm }
69
+ - { platform: arm64-darwin, os: macos-14 }
70
+ - { platform: x64-mingw-ucrt, os: windows-latest }
71
+ steps:
72
+ - uses: actions/checkout@v6
73
+ with:
74
+ submodules: recursive
75
+ - name: Ensure cmake is available
76
+ uses: lukka/get-cmake@latest
77
+ - uses: ruby/setup-ruby@v1
78
+ with:
79
+ ruby-version: ${{ matrix.ruby }}
80
+ bundler-cache: true
81
+ - name: Compile (builds vendored Lexbor, then the ext)
82
+ run: bundle exec rake compile
83
+ - name: Smoke-load
84
+ shell: bash
85
+ run: bundle exec ruby -Ilib -r makiri -e 'p Makiri::VERSION'
86
+ - name: Stage the compiled binary under its ABI subdir
87
+ shell: bash
88
+ run: |
89
+ minor="$(ruby -e 'print RUBY_VERSION[/\d+\.\d+/]')"
90
+ dlext="$(ruby -e 'print RbConfig::CONFIG["DLEXT"]')" # so | bundle
91
+ mkdir -p "artifact/${minor}"
92
+ cp "lib/makiri/makiri.${dlext}" "artifact/${minor}/makiri.${dlext}"
93
+ ls -l "artifact/${minor}"
94
+ - name: Assert the binary is Ruby-portable (no libruby hard-link)
95
+ shell: bash
96
+ # extconf.rb builds the extension with dynamic_lookup / no libruby link so
97
+ # one precompiled binary loads on any compatible Ruby of that ABI. If a
98
+ # change reintroduces a libruby dependency, the gem would only load on the
99
+ # exact build Ruby — fail loudly here instead of shipping a broken gem.
100
+ run: |
101
+ dlext="$(ruby -e 'print RbConfig::CONFIG["DLEXT"]')"
102
+ bin="lib/makiri/makiri.${dlext}"
103
+ case "$(uname -s)" in
104
+ Darwin)
105
+ if otool -L "$bin" | grep -i 'libruby'; then
106
+ echo "::error::$bin hard-links libruby; precompiled gem would not load on other Ruby installs"; exit 1
107
+ fi
108
+ ;;
109
+ MINGW*|MSYS*|CYGWIN*)
110
+ # Windows PE DLLs MUST import the Ruby DLL via the import library
111
+ # (no dynamic_lookup on PE/COFF). A libruby import here is by design,
112
+ # not a portability bug, so this assertion does not apply.
113
+ echo "Windows (x64-mingw-ucrt): Ruby import-library link is by design; skipping libruby assertion"
114
+ ;;
115
+ *)
116
+ if readelf -d "$bin" 2>/dev/null | grep -iE 'NEEDED.*libruby'; then
117
+ echo "::error::$bin has a libruby NEEDED entry; precompiled gem may not load on a static Ruby"; exit 1
118
+ fi
119
+ ;;
120
+ esac
121
+ echo "OK: portability check complete for $bin"
122
+ - uses: actions/upload-artifact@v7
123
+ with:
124
+ name: bin-${{ matrix.platform }}-${{ matrix.ruby }}
125
+ path: artifact/
126
+ if-no-files-found: error
127
+
128
+ # --- assemble one fat gem per platform from its per-ABI binaries ------------
129
+ native-gem:
130
+ name: Native gem ${{ matrix.platform }}
131
+ needs: compile
132
+ runs-on: ${{ matrix.os }}
133
+ strategy:
134
+ fail-fast: false
135
+ matrix:
136
+ include:
137
+ - platform: x86_64-linux
138
+ os: ubuntu-latest
139
+ - platform: aarch64-linux
140
+ os: ubuntu-24.04-arm
141
+ - platform: arm64-darwin
142
+ os: macos-14
143
+ - platform: x64-mingw-ucrt
144
+ os: windows-latest
145
+ steps:
146
+ - uses: actions/checkout@v6
147
+ with:
148
+ submodules: recursive # for the gemspec's file scan (ext/vendor are then dropped)
149
+ - uses: ruby/setup-ruby@v1
150
+ with:
151
+ ruby-version: "3.4"
152
+ - name: Download this platform's per-ABI binaries
153
+ uses: actions/download-artifact@v8
154
+ with:
155
+ pattern: bin-${{ matrix.platform }}-*
156
+ merge-multiple: true
157
+ path: staged
158
+ - name: Place binaries under lib/makiri/<abi>/
159
+ shell: bash
160
+ run: |
161
+ mkdir -p lib/makiri
162
+ echo "Downloaded artifacts:"
163
+ find staged -maxdepth 3 -type f -print || true
164
+ if ! find staged -mindepth 2 -maxdepth 2 -name 'makiri.*' | grep -q .; then
165
+ echo "::error::No makiri binaries were downloaded for ${{ matrix.platform }}"
166
+ exit 1
167
+ fi
168
+ cp -r staged/* lib/makiri/
169
+ echo "Staged binaries:"
170
+ find lib/makiri -maxdepth 2 -name 'makiri.*' -print
171
+ - name: Build the native gem
172
+ run: ruby script/build_native_gem.rb "${{ matrix.platform }}"
173
+ - name: Verify it installs without recompiling
174
+ shell: bash
175
+ run: |
176
+ gem install "makiri-"*"-${{ matrix.platform }}.gem" --local
177
+ ruby -e 'require "makiri"; puts Makiri::VERSION'
178
+ - uses: actions/upload-artifact@v7
179
+ with:
180
+ name: gem-${{ matrix.platform }}
181
+ path: "*.gem"
182
+ if-no-files-found: error
183
+
184
+ # --- attach every gem to a GitHub Release (on a version tag) ----------------
185
+ release:
186
+ name: GitHub Release
187
+ needs: [source-gem, native-gem]
188
+ if: startsWith(github.ref, 'refs/tags/')
189
+ runs-on: ubuntu-latest
190
+ steps:
191
+ - uses: actions/download-artifact@v8
192
+ with:
193
+ pattern: gem-*
194
+ merge-multiple: true
195
+ path: dist
196
+ - name: List assets
197
+ run: ls -l dist
198
+ - name: Create / update the release
199
+ env:
200
+ GH_TOKEN: ${{ github.token }}
201
+ run: |
202
+ # Mark pre-release tags (v1.2.3.rc1 / -beta / -alpha / -pre) as such.
203
+ pre=""
204
+ case "$GITHUB_REF_NAME" in
205
+ *rc*|*beta*|*alpha*|*pre*) pre="--prerelease" ;;
206
+ esac
207
+ gh release create "${GITHUB_REF_NAME}" dist/*.gem \
208
+ --repo "${GITHUB_REPOSITORY}" \
209
+ --title "${GITHUB_REF_NAME}" \
210
+ --notes "Automated release for ${GITHUB_REF_NAME}. See CHANGELOG.md." \
211
+ $pre --verify-tag || \
212
+ gh release upload "${GITHUB_REF_NAME}" dist/*.gem --repo "${GITHUB_REPOSITORY}" --clobber
213
+
214
+ # --- publish to RubyGems, behind the `rubygems` environment approval gate ---
215
+ # Held until the `rubygems` environment's Required-reviewers rule is approved,
216
+ # so a tag push releases on GitHub immediately but the RubyGems push waits.
217
+ #
218
+ # Auth is RubyGems Trusted Publishing (OIDC): no stored API key. Configure a
219
+ # matching Trusted Publisher on RubyGems.org (owner=takahashim, repo=makiri,
220
+ # workflow=release.yml, Environment=rubygems) so the token is only accepted
221
+ # through this gated environment.
222
+ publish:
223
+ name: Publish to RubyGems
224
+ needs: [source-gem, native-gem]
225
+ if: >-
226
+ startsWith(github.ref, 'refs/tags/') ||
227
+ (github.event_name == 'workflow_dispatch' && inputs.publish_to_rubygems)
228
+ runs-on: ubuntu-latest
229
+ environment: rubygems
230
+ permissions:
231
+ contents: read
232
+ id-token: write # OIDC identity token for Trusted Publishing
233
+ steps:
234
+ - uses: ruby/setup-ruby@v1
235
+ with:
236
+ ruby-version: "3.4"
237
+ - uses: actions/download-artifact@v8
238
+ with:
239
+ pattern: gem-*
240
+ merge-multiple: true
241
+ path: dist
242
+ - name: Configure RubyGems credentials (OIDC trusted publishing)
243
+ uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0
244
+ - name: gem push
245
+ run: |
246
+ for g in dist/*.gem; do
247
+ echo "Pushing $g"
248
+ gem push "$g"
249
+ done