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 3, 2025
1 parent 602cdd9 commit 0ffe705
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 1 deletion.
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
13 changes: 12 additions & 1 deletion lib/appraisal/bundler_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class BundlerDSL
attr_reader :dependencies

PARTS = %w[source ruby_version gits paths dependencies groups
platforms source_blocks install_if gemspec]
platforms source_blocks install_if gemspec eval_gemfile]

def initialize
@sources = []
Expand All @@ -21,12 +21,17 @@ def initialize
@source_blocks = {}
@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 @@ -103,6 +108,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
73 changes: 73 additions & 0 deletions spec/acceptance/eval_gemfile_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 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
Dir.mkdir("tmp/stage/gemfiles") rescue nil

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 = ".")
Dir.mkdir("tmp/stage/#{path}") rescue nil

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 0ffe705

Please sign in to comment.