mirror of
https://github.com/ruby/ruby.git
synced 2026-01-27 04:24:23 +00:00
[ruby/rubygems] Add --ext=go to bundle gem
(https://github.com/ruby/rubygems/pull/8183) * Add new gem templates * Add `--ext=go` in `bundle gem` * Add setup-go to .github/workflows/main.yml * Embed go version in go.mod * Use go in bundler CI * Add example method to template * Install Go in .circleci/config.yml * Install Go in .gitlab-ci.yml * Allow hard tabs in go template * Run `rake update_manifest` * Fix test * Move go_gem to gemspec Respect to 9b0ec80 * nits: ⛳ * includes valid module name in go.mod * generate header file * Run `go mod tidy` to create `go.sum` * Check if `go.sum` is generated only when Go is installed To avoid test failure in environments where Go is not installed * Run CI * Workaround for hung up c.f. https://github.com/rubygems/rubygems/actions/runs/11639408044/job/32415545422 * Write man for --ext=go * Re-generate man with `./bin/rake man:build` * pinning 📌 * Update with `./bin/rake man:build` * nits: Extract to method * nits: Use `sys_exec` instead of `system` * Clean go module cache after test Workaround following error ``` 1) bundle gem gem naming with underscore --ext parameter set with go includes go_gem extension in extconf.rb Failure/Error: FileUtils.rm_r(dir) Errno::EACCES: Permission denied @ apply2files - /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go # ./spec/support/helpers.rb:37:in `block in reset!' # ./spec/support/helpers.rb:21:in `each' # ./spec/support/helpers.rb:21:in `reset!' # ./spec/spec_helper.rb:130:in `block (2 levels) in <top (required)>' # /home/runner/work/rubygems/rubygems/lib/rubygems.rb:303:in `load' # /home/runner/work/rubygems/rubygems/lib/rubygems.rb:303:in `activate_and_load_bin_path' ``` Files installed with `go get` have permissions set to 444 ref. https://github.com/golang/go/issues/35615 ``` $ ls -l /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go -r--r--r-- 1 runner runner 42320 Nov 15 06:38 /home/runner/work/rubygems/rubygems/bundler/tmp/2.2/home/go/pkg/mod/gopkg.in/yaml.v3@v3.0.1/decode_test.go ``` So they cannot be deleted by `FileUtils.rm_r`. Therefore, this is necessary to execute `go clean -modcache` separately from `FileUtils.rm_r` to circumvent it. * Remove needless changes ref. https://github.com/ruby/rubygems/pull/8183#discussion_r2532902051 * ci: setup-go is needless * Don't run go command in `bundle gem` ref. https://github.com/ruby/rubygems/pull/8183#discussion_r2532765470 * Revert unrelated date changes --------- https://github.com/ruby/rubygems/commit/260d7d60b3 Co-authored-by: Hiroshi SHIBATA <hsbt@ruby-lang.org>
This commit is contained in:
parent
0f89fa97e3
commit
4423facbff
@ -11,7 +11,7 @@ module Bundler
|
||||
|
||||
AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze
|
||||
PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze
|
||||
EXTENSIONS = ["c", "rust"].freeze
|
||||
EXTENSIONS = ["c", "rust", "go"].freeze
|
||||
|
||||
COMMAND_ALIASES = {
|
||||
"check" => "c",
|
||||
|
||||
@ -13,6 +13,8 @@ module Bundler
|
||||
"test-unit" => "3.0",
|
||||
}.freeze
|
||||
|
||||
DEFAULT_GITHUB_USERNAME = "[USERNAME]"
|
||||
|
||||
attr_reader :options, :gem_name, :thor, :name, :target, :extension
|
||||
|
||||
def initialize(options, gem_name, thor)
|
||||
@ -72,7 +74,7 @@ module Bundler
|
||||
bundle: options[:bundle],
|
||||
bundler_version: bundler_dependency_version,
|
||||
git: use_git,
|
||||
github_username: github_username.empty? ? "[USERNAME]" : github_username,
|
||||
github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username,
|
||||
required_ruby_version: required_ruby_version,
|
||||
rust_builder_required_rubygems_version: rust_builder_required_rubygems_version,
|
||||
minitest_constant_name: minitest_constant_name,
|
||||
@ -231,6 +233,18 @@ module Bundler
|
||||
)
|
||||
end
|
||||
|
||||
if extension == "go"
|
||||
templates.merge!(
|
||||
"ext/newgem/go.mod.tt" => "ext/#{name}/go.mod",
|
||||
"ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb",
|
||||
"ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
|
||||
"ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go",
|
||||
"ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c",
|
||||
)
|
||||
|
||||
config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username]
|
||||
end
|
||||
|
||||
if target.exist? && !target.directory?
|
||||
Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`."
|
||||
exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError]
|
||||
|
||||
@ -38,8 +38,8 @@ Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this op
|
||||
\fB\-\-no\-changelog\fR
|
||||
Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
|
||||
.TP
|
||||
\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR
|
||||
Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
|
||||
\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR
|
||||
Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
|
||||
.TP
|
||||
\fB\-\-no\-ext\fR
|
||||
Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
|
||||
|
||||
@ -51,8 +51,8 @@ configuration file using the following names:
|
||||
Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the
|
||||
global config).
|
||||
|
||||
* `--ext=c`, `--ext=rust`:
|
||||
Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior
|
||||
* `--ext=c`, `--ext=go`, `--ext=rust`:
|
||||
Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior
|
||||
is disabled by default.
|
||||
|
||||
* `--no-ext`:
|
||||
|
||||
@ -6,6 +6,10 @@ jobs:
|
||||
<%- if config[:ext] == 'rust' -%>
|
||||
environment:
|
||||
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
environment:
|
||||
GO_VERSION: '1.23.0'
|
||||
<%- end -%>
|
||||
steps:
|
||||
- checkout
|
||||
@ -16,6 +20,14 @@ jobs:
|
||||
- run:
|
||||
name: Install a RubyGems version that can compile rust extensions
|
||||
command: gem update --system '<%= ::Gem.rubygems_version %>'
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
- run:
|
||||
name: Install Go
|
||||
command: |
|
||||
wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
|
||||
tar -C /usr/local -xzf /tmp/go.tar.gz
|
||||
echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV"
|
||||
<%- end -%>
|
||||
- run:
|
||||
name: Run the default task
|
||||
|
||||
11
lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt
Normal file
11
lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mkmf"
|
||||
require "go_gem/mkmf"
|
||||
|
||||
# Makes all symbols private by default to avoid unintended conflict
|
||||
# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
|
||||
# selectively, or entirely remove this flag.
|
||||
append_cflags("-fvisibility=hidden")
|
||||
|
||||
create_go_makefile(<%= config[:makefile_path].inspect %>)
|
||||
5
lib/bundler/templates/newgem/ext/newgem/go.mod.tt
Normal file
5
lib/bundler/templates/newgem/ext/newgem/go.mod.tt
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/<%= config[:go_module_username] %>/<%= config[:underscored_name] %>
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/ruby-go-gem/go-gem-wrapper latest
|
||||
2
lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt
Normal file
2
lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt
Normal file
@ -0,0 +1,2 @@
|
||||
#include "<%= config[:underscored_name] %>.h"
|
||||
#include "_cgo_export.h"
|
||||
31
lib/bundler/templates/newgem/ext/newgem/newgem.go.tt
Normal file
31
lib/bundler/templates/newgem/ext/newgem/newgem.go.tt
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include "<%= config[:underscored_name] %>.h"
|
||||
|
||||
VALUE rb_<%= config[:underscored_name] %>_sum(VALUE self, VALUE a, VALUE b);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/ruby-go-gem/go-gem-wrapper/ruby"
|
||||
)
|
||||
|
||||
//export rb_<%= config[:underscored_name] %>_sum
|
||||
func rb_<%= config[:underscored_name] %>_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE {
|
||||
longA := ruby.NUM2LONG(ruby.VALUE(a))
|
||||
longB := ruby.NUM2LONG(ruby.VALUE(b))
|
||||
|
||||
sum := longA + longB
|
||||
|
||||
return C.VALUE(ruby.LONG2NUM(sum))
|
||||
}
|
||||
|
||||
//export Init_<%= config[:underscored_name] %>
|
||||
func Init_<%= config[:underscored_name] %>() {
|
||||
rb_m<%= config[:constant_array].join %> := ruby.RbDefineModule(<%= config[:constant_name].inspect %>)
|
||||
ruby.RbDefineSingletonMethod(rb_m<%= config[:constant_array].join %>, "sum", C.rb_<%= config[:underscored_name] %>_sum, 2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
}
|
||||
@ -34,6 +34,12 @@ jobs:
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ext/<%= config[:underscored_name] %>/go.mod
|
||||
<%- end -%>
|
||||
- name: Run the default task
|
||||
run: bundle exec rake
|
||||
|
||||
@ -5,6 +5,11 @@ default:
|
||||
<%- if config[:ext] == 'rust' -%>
|
||||
- apt-get update && apt-get install -y clang
|
||||
- gem update --system '<%= ::Gem.rubygems_version %>'
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
- wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
|
||||
- tar -C /usr/local -xzf /tmp/go.tar.gz
|
||||
- export PATH=/usr/local/go/bin:$PATH
|
||||
<%- end -%>
|
||||
- gem install bundler -v <%= Bundler::VERSION %>
|
||||
- bundle install
|
||||
@ -13,6 +18,10 @@ example_job:
|
||||
<%- if config[:ext] == 'rust' -%>
|
||||
variables:
|
||||
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
variables:
|
||||
GO_VERSION: '1.23.0'
|
||||
<%- end -%>
|
||||
script:
|
||||
- bundle exec rake
|
||||
|
||||
@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%>
|
||||
<%- if %w(c rust go).include?(config[:ext]) -%>
|
||||
spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"]
|
||||
<%- end -%>
|
||||
|
||||
@ -47,6 +47,9 @@ Gem::Specification.new do |spec|
|
||||
<%- if config[:ext] == 'rust' -%>
|
||||
spec.add_dependency "rb_sys", "~> 0.9.91"
|
||||
<%- end -%>
|
||||
<%- if config[:ext] == 'go' -%>
|
||||
spec.add_dependency "go_gem", "~> 0.2"
|
||||
<%- end -%>
|
||||
|
||||
# For more information and examples about making a new gem, check out our
|
||||
# guide at: https://bundler.io/guides/creating_gem.html
|
||||
|
||||
@ -31,6 +31,13 @@ RSpec.describe "bundle gem" do
|
||||
matched[:ignored]&.split(" ")
|
||||
end
|
||||
|
||||
def installed_go?
|
||||
sys_exec("go version", raise_on_error: true)
|
||||
true
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) }
|
||||
|
||||
let(:gem_name) { "mygem" }
|
||||
@ -1748,6 +1755,150 @@ RSpec.describe "bundle gem" do
|
||||
expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
|
||||
end
|
||||
end
|
||||
|
||||
context "--ext parameter set with go" do
|
||||
let(:flags) { "--ext=go" }
|
||||
|
||||
before do
|
||||
bundle ["gem", gem_name, flags].compact.join(" ")
|
||||
end
|
||||
|
||||
after do
|
||||
sys_exec("go clean -modcache", raise_on_error: true) if installed_go?
|
||||
end
|
||||
|
||||
it "is not deprecated" do
|
||||
expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated."
|
||||
end
|
||||
|
||||
it "builds ext skeleton" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go")).to exist
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod")).to exist
|
||||
end
|
||||
|
||||
it "includes extconf.rb in gem_name.gemspec" do
|
||||
expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(%(spec.extensions = ["ext/#{gem_name}/extconf.rb"]))
|
||||
end
|
||||
|
||||
it "includes go_gem in gem_name.gemspec" do
|
||||
expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "go_gem", "~> 0.2"')
|
||||
end
|
||||
|
||||
it "includes go_gem extension in extconf.rb" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(<<~RUBY)
|
||||
require "mkmf"
|
||||
require "go_gem/mkmf"
|
||||
RUBY
|
||||
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(%(create_go_makefile("#{gem_name}/#{gem_name}")))
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).not_to include("create_makefile")
|
||||
end
|
||||
|
||||
it "includes go_gem extension in gem_name.c" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c").read).to eq(<<~C)
|
||||
#include "#{gem_name}.h"
|
||||
#include "_cgo_export.h"
|
||||
C
|
||||
end
|
||||
|
||||
it "includes skeleton code in gem_name.go" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
|
||||
/*
|
||||
#include "#{gem_name}.h"
|
||||
|
||||
VALUE rb_#{gem_name}_sum(VALUE self, VALUE a, VALUE b);
|
||||
*/
|
||||
import "C"
|
||||
GO
|
||||
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
|
||||
//export rb_#{gem_name}_sum
|
||||
func rb_#{gem_name}_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE {
|
||||
GO
|
||||
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO)
|
||||
//export Init_#{gem_name}
|
||||
func Init_#{gem_name}() {
|
||||
GO
|
||||
end
|
||||
|
||||
it "includes valid module name in go.mod" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}")
|
||||
end
|
||||
|
||||
context "with --no-ci" do
|
||||
let(:flags) { "--ext=go --no-ci" }
|
||||
|
||||
it_behaves_like "CI config is absent"
|
||||
end
|
||||
|
||||
context "--ci set to github" do
|
||||
let(:flags) { "--ext=go --ci=github" }
|
||||
|
||||
it "generates .github/workflows/main.yml" do
|
||||
expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist
|
||||
expect(bundled_app("#{gem_name}/.github/workflows/main.yml").read).to include("go-version-file: ext/#{gem_name}/go.mod")
|
||||
end
|
||||
end
|
||||
|
||||
context "--ci set to circle" do
|
||||
let(:flags) { "--ext=go --ci=circle" }
|
||||
|
||||
it "generates a .circleci/config.yml" do
|
||||
expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist
|
||||
|
||||
expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML.strip)
|
||||
environment:
|
||||
GO_VERSION:
|
||||
YAML
|
||||
|
||||
expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML)
|
||||
- run:
|
||||
name: Install Go
|
||||
command: |
|
||||
wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
|
||||
tar -C /usr/local -xzf /tmp/go.tar.gz
|
||||
echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV"
|
||||
YAML
|
||||
end
|
||||
end
|
||||
|
||||
context "--ci set to gitlab" do
|
||||
let(:flags) { "--ext=go --ci=gitlab" }
|
||||
|
||||
it "generates a .gitlab-ci.yml" do
|
||||
expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist
|
||||
|
||||
expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML)
|
||||
- wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
|
||||
- tar -C /usr/local -xzf /tmp/go.tar.gz
|
||||
- export PATH=/usr/local/go/bin:$PATH
|
||||
YAML
|
||||
|
||||
expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML.strip)
|
||||
variables:
|
||||
GO_VERSION:
|
||||
YAML
|
||||
end
|
||||
end
|
||||
|
||||
context "without github.user" do
|
||||
before do
|
||||
# FIXME: GitHub Actions Windows Runner hang up here for some reason...
|
||||
skip "Workaround for hung up" if Gem.win_platform?
|
||||
|
||||
git("config --global --unset github.user")
|
||||
bundle ["gem", gem_name, flags].compact.join(" ")
|
||||
end
|
||||
|
||||
it "includes valid module name in go.mod" do
|
||||
expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/username/#{gem_name}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "gem naming with dashed" do
|
||||
|
||||
@ -20,6 +20,9 @@ RSpec.describe "The library itself" do
|
||||
end
|
||||
|
||||
def check_for_tab_characters(filename)
|
||||
# Because Go uses hard tabs
|
||||
return if filename.end_with?(".go.tt")
|
||||
|
||||
failing_lines = []
|
||||
each_line(filename) do |line, number|
|
||||
failing_lines << number + 1 if line.include?("\t")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user