Skip to content

Commit

Permalink
✨ Support eval_gemfile
Browse files Browse the repository at this point in the history
  • Loading branch information
pboling committed Feb 21, 2025
1 parent 975ded0 commit 774054c
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 0 deletions.
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,90 @@ When you prefix a command with `appraisal`, the command is run with the
appropriate Gemfile for that appraisal, ensuring the correct dependencies
are used.

Sharing Modular Gemfiles between Appraisals
-------

_New for version 3.0_

It is common for Appraisals to duplicate sets of gems, and sometimes it
makes sense to DRY this up into a shared, modular, gemfile.
In a scenario where you do not load your main Gemfile in your Appraisals,
but you want to declare your various gem sets for e.g.
`%w(coverage test documentation audit)` once each, you can re-use the same
modular gemfiles for local development by referencing them from the main
Gemfile.

To do this, use the `eval_gemfile` declaration within the necessary
`appraise` block in your `Appraisals` file, which will behave the same as
`eval_gemfile` does in a normal Gemfile.

### Example Usage

You could put your modular gemfiles in the `gemfiles` directory, or nest
them in `gemfiles/modular/*`, which will be used for this example.

**Gemfile**
```ruby
eval_gemfile "gemfiles/modular/audit.gemfile"
```

**gemfiles/modular/audit.gemfile**
```ruby
# Many gems are dropping support for Ruby < 3.1,
# so we only want to run our security audit in CI on Ruby 3.1+
gem "bundler-audit", "~> 0.9.2"
# And other security audit gems...
```

**Appraisals**
```ruby
appraise "ruby-2-7" do
gem "dummy"
end

appraise "ruby-3-0" do
gem "dummy"
end

appraise "ruby-3-1" do
gem "dummy"
eval_gemfile "modular/audit.gemfile"
end

appraise "ruby-3-2" do
gem "dummy"
eval_gemfile "modular/audit.gemfile"
end

appraise "ruby-3-3" do
gem "dummy"
eval_gemfile "modular/audit.gemfile"
end

appraise "ruby-3-4" do
gem "dummy"
eval_gemfile "modular/audit.gemfile"
end
```

**Appraisal.root.gemfile**
```ruby
source "https://rubygems.org"

# Appraisal Root Gemfile is for running appraisal to generate the Appraisal Gemfiles
# We do not load the standard Gemfile, as it is tailored for local development,
# while appraisals are tailored for CI.

gemspec

gem "appraisal"
```

Now when you need to update your appraisals:
```shell
BUNDLE_GEMFILE=Appraisal.root.gemfile bundle exec appraisal update
```

Removing Gems using Appraisal
-------

Expand Down
4 changes: 4 additions & 0 deletions lib/appraisal/appraisal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def initialize(name, source_gemfile)
@gemfile = source_gemfile.dup
end

def eval_gemfile(*args)
gemfile.eval_gemfile(*args)
end

def gem(*args)
gemfile.gem(*args)
end
Expand Down
12 changes: 12 additions & 0 deletions lib/appraisal/bundler_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class BundlerDSL
source_blocks
install_if
gemspec
eval_gemfile
]

def initialize
Expand All @@ -32,12 +33,17 @@ def initialize
@source_blocks = OrderedHash.new
@git_sources = {}
@install_if = {}
@eval_gemfile = []
end

def run(&block)
instance_exec(&block)
end

def eval_gemfile(path, contents = nil)
@eval_gemfile << [path, contents]
end

def gem(name, *requirements)
@dependencies.add(name, substitute_git_source(requirements))
end
Expand Down Expand Up @@ -114,6 +120,12 @@ def git_source(source, &block)

private

def eval_gemfile_entry
@eval_gemfile.map { |(p, c)| "eval_gemfile(#{p.inspect}#{", #{c.inspect}" if c})" } * "\n\n"
end

alias_method :eval_gemfile_entry_for_dup, :eval_gemfile_entry

def source_entry
@sources.uniq.map { |source| "source #{source.inspect}" }.join("\n")
end
Expand Down
81 changes: 81 additions & 0 deletions spec/acceptance/eval_gemfile_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe "eval_gemfile" do
before do
build_appraisal_file
build_modular_gemfile
build_rakefile
build_gemspec
end

it "supports eval_gemfile syntax" do
write_file "Gemfile", <<-GEMFILE
source "https://rubygems.org"
gem 'appraisal', :path => #{PROJECT_ROOT.inspect}
gemspec
GEMFILE

run "bundle install --local"
run "appraisal install"
output = run "appraisal rake version"

expect(output).to include "Loaded 1.1.0"
end

def build_modular_gemfile
begin
Dir.mkdir("tmp/stage/gemfiles")
rescue
nil
end

write_file File.join("gemfiles", "im_with_dummy"), <<-GEMFILE
# No source needed because this is a modular gemfile intended to be loaded into another gemfile,
# which will define source.
gem 'dummy'
GEMFILE
end

def build_appraisal_file
super(<<-APPRAISALS)
appraise 'stock' do
gem 'rake'
eval_gemfile "im_with_dummy"
end
APPRAISALS
end

def build_rakefile
write_file "Rakefile", <<-RAKEFILE
require 'rubygems'
require 'bundler/setup'
require 'appraisal'
task :version do
require 'dummy'
puts "Loaded \#{$dummy_version}"
end
RAKEFILE
end

def build_gemspec(path = ".")
begin
Dir.mkdir("tmp/stage/#{path}")
rescue
nil
end

write_file File.join(path, "gemspec_project.gemspec"), <<-GEMSPEC
Gem::Specification.new do |s|
s.name = 'gemspec_project'
s.version = '0.1'
s.summary = 'Awesome Gem!'
s.authors = "Appraisal"
end
GEMSPEC
end
end

0 comments on commit 774054c

Please sign in to comment.