cibuildgem 0.2.2 โ†’ 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a435af5eac0a64303ce3cc170330faa6d10c7baef2af54c7c7df99713766fd4
4
- data.tar.gz: b2e59e31200a2a3490a15d033730156da13e1cf42a54ad6e175d5f8475717df3
3
+ metadata.gz: 2d98ae7e4d9c1c2bd075ee978c39d292fa995d5162d018f399e217e58ac90501
4
+ data.tar.gz: e497b8a81dc7b395b20f7c9f94db94a937b4c55de4e331fbccd7e172d3cf3807
5
5
  SHA512:
6
- metadata.gz: cf049b9d6bcbddaca238f847077dd3b3bf335df62674236b4c36a44a68931a315f32b344bc100d3871cc8fc5161c101c29cfc2e2b2195870aa18d1268cfec5ab
7
- data.tar.gz: c7782b3afcb6f1c8a871c18f0199fb95fe9e4b77b74ab8e5b6aa2e1c75b872bd4a994077a0460bbbe40c9b4e93cc1633b6c58e11b48e4109538b65ffc22a9fe5
6
+ metadata.gz: 5255e73551d7a8237dce25300b64f00845f196348443684c7c4fc9fc42e3b57818d3bf30eee5989cd84fac6e610f96dfdf75db4353b0ed4d54c141f3eb124abc
7
+ data.tar.gz: f15c1eab3cbc400375a9e561a452f5ad70f10e09b068433a4eb4589a10dac3cedf37b6e20f6b49b38ab286a65847f8b1371c853b78a483ce4e19abca7568368e
data/README.md CHANGED
@@ -22,49 +22,22 @@ As noted by [@flavorjones](https://github.com/flavorjones), this toolchain works
22
22
 
23
23
  ## ๐Ÿ’ป cibuildgem
24
24
 
25
- > [!NOTE]
26
- > cibuildgem is for now not able to compile projects that needs to link on external libraries. Unless the project vendors those libraries or uses [mini_portile](https://github.com/flavorjones/mini_portile).
27
-
28
- ### How to use it
29
-
30
- While cibuildgem is generally **not** meant to be used locally, it provides a command to generate the right GitHub workflow for your project:
31
-
32
- 1. Install cibuildgem: `gem install cibuildgem`
33
- 2. Generate the workflow: `cd` in your gem's folder and run `cibuildgem ci_template`
34
- 3. Commit the `.github/workflows/cibuildgem.yaml` file.
35
-
36
- ### Triggering the workflow
37
-
38
- Once pushed in your repository **default** branch, the workflow that we just generated is actionable manually on the GitHub action page. It will run in sequence:
39
-
40
- 1. Compile the gem on the target platform (defaults to MacOS ARM, MacOS Intel, Windows, Ubuntu 24)
41
- 2. Once the compilation succeeds on all platform, it proceeds to run the test suite on the target platform. This will trigger many CI steps as the testing matrix is big.
42
- 3. Once the test suite passes for all platforms and all Ruby versions the gem is compatible with, the action proceeds to installing the gem we just packaged. This step ensure that the gem is actually installable.
43
- 4. [OPTIONAL] When trigering the workflow manually, you can tick the box to automatically release the gems that were packaged. This works using the RubyGems trusted publisher feature (documentation to write later). If you do no want the tool to make the release, you can download all the GitHub artifacts that were uploaded. It will contain all the gems with precompiled binaries in the `pkg` folder. You are free to download them locally and release them yourself from your machine.
25
+ Head to the [documentation Wiki](https://github.com/Shopify/cibuildgem/wiki) to setup and configure cibuildgem for your gem.
44
26
 
27
+ ## Working examples
45
28
 
46
- ### Changes to make in your gem to support precompiled binaries
29
+ Here are some working examples on gem that have setup cibuildgem
47
30
 
48
- Due to the RubyGems specification, we can't release a gem with precompiled binaries for a specific Ruby version. Because the Ruby ABI is incompatible between minor versions, Rake Compiler (the tool underneath cibuildgem), compiles the binary for every minor Ruby versions your gem supports. All those binaries will be packaged in the gem (called a fat gem) in different folder such as `3.0/date.so`, `3.1/date.so` etc...
49
- At runtime, your gem need to require the right binary based on the running ruby version.
31
+ | Name | Example Run | Published Gem |
32
+ |-----------|-------------|---------------|
33
+ | [Rubydex](https://github.com/shopify/rubydex) | [CI Run](https://github.com/Shopify/rubydex/actions/runs/21880161517) | [Gem](https://rubygems.org/gems/rubydex/versions/0.1.0.beta4-arm64-darwin) |
34
+ | [Heap Profiler](https://github.com/shopify/heap-profiler) | [CI Run](https://github.com/Shopify/heap-profiler/actions/runs/20996043558) | [Gem](https://rubygems.org/gems/heap-profiler/versions/0.8.0.rc1-x86_64-linux) |
35
+ | [djb2](https://github.com/Shopify/djb2) | [CI Run](https://github.com/Shopify/djb2/actions/runs/22188688549) | [Gem](https://rubygems.org/gems/djb2/versions/0.1.1-x86_64-linux) |
36
+ | [Blake3](https://github.com/Shopify/blake3-rb) | [CI Run](https://github.com/Shopify/blake3-rb/actions/runs/21253662535) | [Gem](https://rubygems.org/gems/blake3-rb/versions/1.5.6.rc1-x86_64-linux) |
37
+ | [Raindrops (fork)](https://github.com/Edouard-chin/raindrops) | [CI Run](https://github.com/Edouard-chin/raindrops/actions/runs/22045845221) | [Gem](https://rubygems.org/gems/precompiled-raindrops) |
38
+ | [Stack Frames](https://github.com/Shopify/stack_frames) | [CI Run](https://github.com/Shopify/stack_frames/actions/runs/19969899178) | [Gem](https://rubygems.org/gems/stack_frames/versions/0.1.4-x86_64-linux) |
50
39
 
51
- ```ruby
52
- # Before
53
-
54
- require 'date_core.so'
55
-
56
- # After
57
-
58
- begin
59
- ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
60
- require "#{ruby_version}/date_core"
61
- rescue LoadError
62
- # It's important to leave for users that can not or don't want to use the gem with precompiled binaries.
63
- require "date_core"
64
- end
65
- ```
66
-
67
- ### Supported platforms/Ruby versions
40
+ ## Supported platforms/Ruby versions
68
41
 
69
42
  | | MacOS Intel | MacOS ARM | Windows x64 UCRT | Linux GNU x86_64|Linux AARCH64 |
70
43
  |---------|------------- | --------- | ------------|-----------------|-----------------|
@@ -72,7 +45,4 @@ end
72
45
  | Ruby 3.2| ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข |
73
46
  | Ruby 3.3| ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข |
74
47
  | Ruby 3.4| ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข | ๐ŸŸข |
75
-
76
- ## ๐Ÿงช Development
77
-
78
- If you'd like to run a end-to-end test, the `date` gem is vendored in this project. You can trigger a manual run to do the whole compile, test, install dance from the GitHub action menu.
48
+ | Ruby 4.0| ๐ŸŸข | ๐ŸŸข | ๐ŸŸ  (not tested)| ๐ŸŸข | ๐ŸŸข |
@@ -3,6 +3,7 @@
3
3
  require "thor"
4
4
  require "rake/extensiontask"
5
5
  require "prism"
6
+ require "open3"
6
7
 
7
8
  module Cibuildgem
8
9
  class CLI < Thor
@@ -10,6 +11,10 @@ module Cibuildgem
10
11
 
11
12
  source_root(File.expand_path("templates", __dir__))
12
13
 
14
+ # RubyGems already restricts a gem's name, version and platform to these characters, so every filename
15
+ # `cibuildgem package` can legitimately produce matches. Anything else was not produced by the package task.
16
+ RELEASABLE_GEM_FILENAME = /\A[A-Za-z0-9._-]+\.gem\z/
17
+
13
18
  class << self
14
19
  def exit_on_failure?
15
20
  true
@@ -31,7 +36,7 @@ module Cibuildgem
31
36
  run_rake_tasks!("cibuildgem:setup", :compile)
32
37
  end
33
38
 
34
- desc "package", "Compile and package a 'fat gem'.", hide: true
39
+ desc "package", "Compile and package a 'multi-ABI gem'.", hide: true
35
40
  long_desc <<~MSG
36
41
  This command should normally run on CI, using the cibuildgem workflow. It will not work locally unless
37
42
  the environment is properly setup.
@@ -41,13 +46,20 @@ module Cibuildgem
41
46
  - A gem without precompiled binary (Ruby platform).
42
47
 
43
48
  The gem with precompiled binaries will be packaged with multiple binaries compatible for different
44
- Ruby ABI (depending on what Ruby version the gem supports).
49
+ Ruby ABI (depending on what Ruby version the gem supports). If `--include-single-abi` is passed,
50
+ cibuildgem will also compile a set of single-ABI gems per Ruby version supported by the gem.
45
51
  MSG
46
52
  method_option "gemspec", type: "string", required: false, desc: "The gemspec to use. Defaults to the gemspec from the current working directory."
53
+ method_option "include-single-abi", type: :boolean, required: false, default: false, desc: "Whether to also compile a set of single-ABI gems. Defaults to false."
47
54
  def package
48
55
  ENV["RUBY_CC_VERSION"] ||= compilation_task.ruby_cc_version
49
-
50
- run_rake_tasks!("cibuildgem:setup", :cross, :native, :gem)
56
+ run_rake_tasks!(
57
+ "cibuildgem:setup",
58
+ :cross,
59
+ :native,
60
+ :gem,
61
+ env: { "CIBUILDGEM_CONTENT_ADDRESSABLE" => options["include-single-abi"] == true ? "true" : "false" },
62
+ )
51
63
  end
52
64
 
53
65
  desc "test", "Run the test suites of the target gem"
@@ -112,14 +124,25 @@ module Cibuildgem
112
124
  pathname = Pathname(file)
113
125
  next if pathname.directory? || pathname.extname != ".gem"
114
126
 
115
- Kernel.system("gem push #{file}", exception: true)
127
+ verify_releasable_filename!(pathname)
128
+
129
+ out, status = Open3.capture2e("gem", "push", file)
130
+ next if status.success?
131
+
132
+ if out =~ /Repushing of gem versions is not allowed/
133
+ puts "Gem #{file} already exists on RubyGems.org, skipping..."
134
+
135
+ next
136
+ end
137
+
138
+ raise out
116
139
  end
117
140
  end
118
141
 
119
142
  desc "print_ruby_cc_version", "Output the cross compile ruby version needed for the gem. For internal usage", hide: true
120
143
  method_option "gemspec", type: "string", required: false, desc: "The gemspec to use. If the option is not passed, a gemspec file from the current working directory will be used."
121
144
  def print_ruby_cc_version
122
- print(compilation_task.ruby_cc_version)
145
+ print(ENV["RUBY_CC_VERSION"] || compilation_task.ruby_cc_version)
123
146
  end
124
147
 
125
148
  desc "normalized_platform", "The platform name for compilation purposes", hide: true
@@ -129,8 +152,20 @@ module Cibuildgem
129
152
 
130
153
  private
131
154
 
132
- def run_rake_tasks!(*tasks)
133
- all_tasks = tasks.join(" ")
155
+ def verify_releasable_filename!(pathname)
156
+ basename = pathname.basename.to_s
157
+ return if RELEASABLE_GEM_FILENAME.match?(basename)
158
+
159
+ raise(<<~MSG)
160
+ Refusing to publish #{basename.inspect}.
161
+
162
+ A gem filename may only contain letters, digits, dots, dashes and underscores, so this file was not
163
+ produced by `cibuildgem package`. Artifacts reach the release job from the compile job, which runs
164
+ without publishing credentials, so an unexpected filename is treated as tampering rather than a gem.
165
+ MSG
166
+ end
167
+
168
+ def run_rake_tasks!(*tasks, env: {})
134
169
  rakelibdir = [File.expand_path("tasks", __dir__), "rakelib"].join(File::PATH_SEPARATOR)
135
170
  rake_compiler_path = Gem.loaded_specs["rake-compiler"].full_require_paths
136
171
  rake_specs = Gem.loaded_specs["rake"]
@@ -141,8 +176,15 @@ module Cibuildgem
141
176
  patch = File.expand_path("../extension_patch.rb", __FILE__)
142
177
 
143
178
  system(
144
- { "RUBYLIB" => load_paths },
145
- "bundle exec #{RbConfig.ruby} #{rake_executable} #{all_tasks} -R#{rakelibdir} -r #{patch}",
179
+ { "RUBYLIB" => load_paths }.merge(env),
180
+ "bundle",
181
+ "exec",
182
+ RbConfig.ruby,
183
+ rake_executable,
184
+ *tasks.map(&:to_s),
185
+ "-R#{rakelibdir}",
186
+ "-r",
187
+ patch,
146
188
  exception: true,
147
189
  )
148
190
  end
@@ -7,18 +7,21 @@ require_relative "create_makefile_finder"
7
7
 
8
8
  module Cibuildgem
9
9
  class CompilationTasks
10
- attr_reader :gemspec, :native, :create_packaging_task, :extension_task
10
+ attr_reader :gemspec, :native, :create_packaging_task, :extension_task, :content_addressable
11
11
 
12
- def initialize(create_packaging_task = false, gemspec = nil)
12
+ def initialize(
13
+ create_packaging_task = false,
14
+ gemspec = nil,
15
+ content_addressable: ENV["CIBUILDGEM_CONTENT_ADDRESSABLE"] == "true"
16
+ )
13
17
  @gemspec = Bundler.load_gemspec(gemspec || find_gemspec)
14
18
  verify_gemspec!
15
19
 
16
20
  @create_packaging_task = create_packaging_task
21
+ @content_addressable = content_addressable
17
22
  end
18
23
 
19
24
  def setup
20
- Rake::ExtensionTask.enable!
21
-
22
25
  gemspec.extensions.each do |path|
23
26
  binary_name = parse_extconf(path)
24
27
  define_task(path, binary_name)
@@ -54,20 +57,35 @@ module Cibuildgem
54
57
  end
55
58
 
56
59
  def define_task(path, binary_name)
57
- @extension_task = Rake::ExtensionTask.new do |ext|
58
- ext.name = File.basename(binary_name)
59
- ext.config_script = File.basename(path)
60
- ext.ext_dir = File.dirname(path)
61
- ext.lib_dir = binary_lib_dir(binary_name) if binary_lib_dir(binary_name)
62
- ext.gem_spec = gemspec
63
- ext.cross_platform = normalized_platform
64
- ext.cross_compile = true
65
- ext.no_native = true
60
+ @extension_task = Rake::ExtensionTask.current || Rake::ExtensionTask.new
61
+
62
+ @extension_task.name = File.basename(binary_name)
63
+ @extension_task.config_script = File.basename(path)
64
+ @extension_task.ext_dir = File.dirname(path)
65
+ @extension_task.lib_dir = binary_lib_dir(binary_name) if binary_lib_dir(binary_name)
66
+ @extension_task.gem_spec = gemspec
67
+ @extension_task.cross_platform = normalized_platform
68
+ @extension_task.cross_compile = true
69
+ @extension_task.no_native = true
70
+ if content_addressable
71
+ if @extension_task.respond_to?(:content_addressable=)
72
+ @extension_task.content_addressable = true
73
+ else
74
+ warn("WARNING: content-addressable packaging was requested, but rake-compiler #{rake_compiler_version} " \
75
+ "does not support it. Upgrade rake-compiler to >= 1.3.2 to build single-ABI gems.")
76
+ end
66
77
  end
67
78
 
79
+ Rake::ExtensionTask.enable!
80
+ @extension_task.define
81
+
68
82
  disable_shared unless Gem.win_platform?
69
83
  end
70
84
 
85
+ def rake_compiler_version
86
+ Gem.loaded_specs["rake-compiler"]&.version&.to_s || "unknown"
87
+ end
88
+
71
89
  def binary_lib_dir(binary_name)
72
90
  dir = File.dirname(binary_name)
73
91
  return if dir == "."
@@ -7,7 +7,7 @@ module Cibuildgem
7
7
  class << self
8
8
  def prepended(mod)
9
9
  class << mod
10
- attr_accessor :enabled
10
+ attr_accessor :enabled, :current
11
11
 
12
12
  def enable!
13
13
  @enabled = true
@@ -16,6 +16,12 @@ module Cibuildgem
16
16
  end
17
17
  end
18
18
 
19
+ def initialize(*)
20
+ super
21
+
22
+ self.class.current = self
23
+ end
24
+
19
25
  def define
20
26
  super if self.class.enabled
21
27
  end
@@ -12,11 +12,9 @@ task "cibuildgem:setup" do
12
12
  end
13
13
 
14
14
  task "copy:stage:lib" do
15
- version = RUBY_VERSION.match(/(\d\.\d)/)[1]
16
- dest = File.join(task.extension_task.lib_dir, version)
17
- src = File.join("tmp", task.extension_task.cross_platform, "stage", dest)
15
+ src = File.join("tmp", task.extension_task.cross_platform, "stage")
18
16
 
19
- cp_r(src, dest, remove_destination: true)
17
+ cp_r(Dir.glob("#{src}/*"), ".", remove_destination: true)
20
18
  end
21
19
 
22
20
  unless Rake::Task.task_defined?(:test)
@@ -7,6 +7,8 @@ on:
7
7
  required: false
8
8
  type: boolean
9
9
  default: false
10
+ env:
11
+ CIBUILDGEM: 1
10
12
  jobs:
11
13
  compile:
12
14
  timeout-minutes: 20
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cibuildgem
4
- VERSION = "0.2.2"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cibuildgem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify