appraisal2 3.0.0 → 3.0.1

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.
@@ -9,12 +9,11 @@ require "appraisal/gemfile"
9
9
  require "appraisal/command"
10
10
  require "appraisal/customize"
11
11
  require "appraisal/utils"
12
+ require "appraisal/gem_manager"
12
13
 
13
14
  module Appraisal
14
15
  # Represents one appraisal and its dependencies
15
16
  class Appraisal
16
- DEFAULT_INSTALL_OPTIONS = {"jobs" => 1}.freeze
17
-
18
17
  attr_reader :name, :gemfile
19
18
 
20
19
  def initialize(name, source_gemfile)
@@ -79,22 +78,15 @@ module Appraisal
79
78
  end
80
79
 
81
80
  def install(options = {})
82
- commands = [install_command(options).join(" ")]
83
-
84
- commands.unshift(check_command.join(" ")) if options["without"].nil? || options["without"].empty?
85
-
86
- command = commands.join(" || ")
87
-
88
- if Bundler.settings[:path]
89
- env = {"BUNDLE_DISABLE_SHARED_GEMS" => "1"}
90
- Command.new(command, :env => env).run
91
- else
92
- Command.new(command).run
93
- end
81
+ gem_manager = options.delete(:gem_manager) || options.delete("gem_manager") ||
82
+ options.delete(:"gem-manager") || options.delete("gem-manager")
83
+ manager = GemManager::Factory.create(gemfile_path, project_root, :manager => gem_manager)
84
+ manager.install(options)
94
85
  end
95
86
 
96
- def update(gems = [])
97
- Command.new(update_command(gems), :gemfile => gemfile_path).run
87
+ def update(gems = [], gem_manager: nil)
88
+ manager = GemManager::Factory.create(gemfile_path, project_root, :manager => gem_manager)
89
+ manager.update(gems)
98
90
  end
99
91
 
100
92
  def gemfile_path
@@ -122,20 +114,6 @@ module Appraisal
122
114
 
123
115
  private
124
116
 
125
- def check_command
126
- gemfile_option = "--gemfile='#{gemfile_path}'"
127
- ["bundle", "check", gemfile_option]
128
- end
129
-
130
- def install_command(options = {})
131
- gemfile_option = "--gemfile='#{gemfile_path}'"
132
- ["bundle", "install", gemfile_option, bundle_options(options)].compact
133
- end
134
-
135
- def update_command(gems)
136
- ["bundle", "update", *gems].compact
137
- end
138
-
139
117
  def gemfile_root
140
118
  project_root + "gemfiles"
141
119
  end
@@ -156,32 +134,6 @@ module Appraisal
156
134
  name.gsub(/[^\w.]/, "_")
157
135
  end
158
136
 
159
- def bundle_options(options)
160
- full_options = DEFAULT_INSTALL_OPTIONS.dup.merge(options)
161
- options_strings = []
162
- jobs = full_options.delete("jobs")
163
- if jobs > 1
164
- if Utils.support_parallel_installation?
165
- options_strings << "--jobs=#{jobs}"
166
- else
167
- warn("Your current version of Bundler does not support parallel installation. Please " \
168
- "upgrade Bundler to version >= 1.4.0, or invoke `appraisal` without `--jobs` option.")
169
- end
170
- end
171
-
172
- path = full_options.delete("path")
173
- if path
174
- relative_path = project_root.join(options["path"])
175
- options_strings << "--path #{relative_path}"
176
- end
177
-
178
- full_options.each do |flag, val|
179
- options_strings << "--#{flag} #{val}"
180
- end
181
-
182
- options_strings.join(" ") if options_strings != []
183
- end
184
-
185
137
  def comment_lines(heading)
186
138
  heading.lines.map do |line|
187
139
  if line.lstrip.empty?
@@ -10,8 +10,10 @@ module Appraisal
10
10
  class AppraisalFile
11
11
  attr_reader :appraisals, :gemfile
12
12
 
13
- def self.each(&block)
14
- new.each(&block)
13
+ class << self
14
+ def each(&block)
15
+ new.each(&block)
16
+ end
15
17
  end
16
18
 
17
19
  def initialize
data/lib/appraisal/cli.rb CHANGED
@@ -8,6 +8,12 @@ module Appraisal
8
8
  default_task :install
9
9
  map ["-v", "--version"] => "version"
10
10
 
11
+ class_option "gem-manager",
12
+ :aliases => "-g",
13
+ :type => :string,
14
+ :default => "bundler",
15
+ :desc => "Gem manager to use for install/update (bundler or ore)"
16
+
11
17
  class << self
12
18
  # Override help command to print out usage
13
19
  def help(shell, subcommand = false)
@@ -74,8 +80,9 @@ module Appraisal
74
80
  def install
75
81
  invoke(:generate, [], {})
76
82
 
83
+ install_options = options.to_h
77
84
  AppraisalFile.each do |appraisal|
78
- appraisal.install(options)
85
+ appraisal.install(install_options)
79
86
  appraisal.relativize
80
87
  end
81
88
  end
@@ -94,10 +101,11 @@ module Appraisal
94
101
 
95
102
  desc "update [LIST_OF_GEMS]", "Remove all generated gemfiles and lockfiles, resolve, and install dependencies again"
96
103
  def update(*gems)
97
- invoke(:generate, [])
104
+ invoke(:generate, [], {})
98
105
 
106
+ gem_manager = options["gem-manager"] || options[:gem_manager]
99
107
  AppraisalFile.each do |appraisal|
100
- appraisal.update(gems)
108
+ appraisal.update(gems, :gem_manager => gem_manager)
101
109
  end
102
110
  end
103
111
 
@@ -126,5 +134,12 @@ module Appraisal
126
134
  end
127
135
  end
128
136
  end
137
+
138
+ def respond_to_missing?(name, include_private = false)
139
+ appraisals = AppraisalFile.new.appraisals
140
+ appraisals.any? { |appraisal| appraisal.name == name.to_s } || super
141
+ rescue AppraisalsNotFound
142
+ super
143
+ end
129
144
  end
130
145
  end
@@ -9,32 +9,40 @@ module Appraisal
9
9
  @@single_quotes = single_quotes
10
10
  end
11
11
 
12
- def self.heading(gemfile = nil)
13
- @@heading ||= nil
14
- return @@heading unless gemfile
12
+ class << self
13
+ def heading(gemfile = nil)
14
+ @@heading ||= nil
15
+ return @@heading unless gemfile
15
16
 
16
- customize(@@heading, gemfile)
17
- end
17
+ customize(@@heading, gemfile)
18
+ end
18
19
 
19
- def self.single_quotes
20
- @@single_quotes ||= false
21
- end
20
+ def single_quotes
21
+ @@single_quotes ||= false
22
+ end
22
23
 
23
- def self.customize(topper, gemfile)
24
- return unless topper
25
-
26
- format(
27
- topper.to_s,
28
- :appraisal => gemfile.send(:clean_name),
29
- :gemfile => gemfile.send(:gemfile_name),
30
- :gemfile_path => gemfile.gemfile_path,
31
- :lockfile => "#{gemfile.send(:gemfile_name)}.lock",
32
- :lockfile_path => gemfile.send(:lockfile_path),
33
- :relative_gemfile_path => gemfile.relative_gemfile_path,
34
- :relative_lockfile_path => "#{gemfile.relative_gemfile_path}.lock",
35
- )
36
- end
24
+ # Reset class state - useful for testing
25
+ def reset!
26
+ @@heading = nil
27
+ @@single_quotes = false
28
+ end
37
29
 
38
- private_class_method :customize
30
+ private
31
+
32
+ def customize(topper, gemfile)
33
+ return unless topper
34
+
35
+ format(
36
+ topper.to_s,
37
+ :appraisal => gemfile.send(:clean_name),
38
+ :gemfile => gemfile.send(:gemfile_name),
39
+ :gemfile_path => gemfile.gemfile_path,
40
+ :lockfile => "#{gemfile.send(:gemfile_name)}.lock",
41
+ :lockfile_path => gemfile.send(:lockfile_path),
42
+ :relative_gemfile_path => gemfile.relative_gemfile_path,
43
+ :relative_lockfile_path => "#{gemfile.relative_gemfile_path}.lock",
44
+ )
45
+ end
46
+ end
39
47
  end
40
48
  end
@@ -7,4 +7,25 @@ module Appraisal
7
7
  "Unable to locate 'Appraisals' file in the current directory."
8
8
  end
9
9
  end
10
+
11
+ # Raises when ore-light gem manager is requested but not installed.
12
+ class OreNotAvailableError < ArgumentError
13
+ def message
14
+ "ore-light is not installed or not in PATH. " \
15
+ "Install from: https://github.com/contriboss/ore-light"
16
+ end
17
+ end
18
+
19
+ # Raises when an unknown gem manager is requested.
20
+ class UnknownGemManagerError < ArgumentError
21
+ def initialize(manager, available)
22
+ @manager = manager
23
+ @available = available
24
+ super()
25
+ end
26
+
27
+ def message
28
+ "Unknown gem manager: '#{@manager}'. Available: #{@available.join(", ")}"
29
+ end
30
+ end
10
31
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appraisal
4
+ module GemManager
5
+ # Abstract base class defining the interface for gem managers.
6
+ # Subclasses must implement all public methods.
7
+ class Base
8
+ attr_reader :gemfile_path, :project_root
9
+
10
+ # @param gemfile_path [String] path to the gemfile
11
+ # @param project_root [Pathname] root directory of the project
12
+ def initialize(gemfile_path, project_root)
13
+ @gemfile_path = gemfile_path
14
+ @project_root = project_root
15
+ end
16
+
17
+ # Install gems for the given gemfile
18
+ # @param options [Hash] install options (jobs, retry, without, path, etc.)
19
+ # @return [void]
20
+ def install(options = {})
21
+ raise NotImplementedError, "#{self.class}#install must be implemented"
22
+ end
23
+
24
+ # Update gems (all or specific gems)
25
+ # @param gems [Array<String>] list of gems to update, empty = all
26
+ # @return [void]
27
+ def update(gems = [])
28
+ raise NotImplementedError, "#{self.class}#update must be implemented"
29
+ end
30
+
31
+ # Name of the gem manager for display
32
+ # @return [String]
33
+ def name
34
+ raise NotImplementedError, "#{self.class}#name must be implemented"
35
+ end
36
+
37
+ # Check if the gem manager is available on the system
38
+ # @return [Boolean]
39
+ def available?
40
+ raise NotImplementedError, "#{self.class}#available? must be implemented"
41
+ end
42
+
43
+ # Validate that the gem manager is available, raising an error if not
44
+ # @raise [OreNotAvailableError] if ore is not available
45
+ # @return [void]
46
+ def validate_availability!
47
+ # Default implementation does nothing (bundler is always available)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Appraisal
6
+ module GemManager
7
+ # Bundler adapter for gem management operations.
8
+ # This is the default gem manager and is always available.
9
+ class BundlerAdapter < Base
10
+ DEFAULT_INSTALL_OPTIONS = {"jobs" => 1}.freeze
11
+
12
+ def name
13
+ "bundler"
14
+ end
15
+
16
+ def available?
17
+ true # Bundler is always available (it's a dependency of appraisal2)
18
+ end
19
+
20
+ def install(options = {})
21
+ commands = [install_command(options).join(" ")]
22
+
23
+ # Only run check command if not using --without option
24
+ if options["without"].nil? || options["without"].empty?
25
+ commands.unshift(check_command.join(" "))
26
+ end
27
+
28
+ command = commands.join(" || ")
29
+
30
+ if Bundler.settings[:path]
31
+ env = {"BUNDLE_DISABLE_SHARED_GEMS" => "1"}
32
+ Command.new(command, :gemfile => gemfile_path, :env => env).run
33
+ else
34
+ Command.new(command, :gemfile => gemfile_path).run
35
+ end
36
+ end
37
+
38
+ def update(gems = [])
39
+ Command.new(update_command(gems), :gemfile => gemfile_path).run
40
+ end
41
+
42
+ private
43
+
44
+ def check_command
45
+ gemfile_option = "--gemfile='#{gemfile_path}'"
46
+ ["bundle", "check", gemfile_option]
47
+ end
48
+
49
+ def install_command(options = {})
50
+ gemfile_option = "--gemfile='#{gemfile_path}'"
51
+ ["bundle", "install", gemfile_option, bundle_options(options)].compact
52
+ end
53
+
54
+ def update_command(gems)
55
+ ["bundle", "update", *gems].compact
56
+ end
57
+
58
+ def bundle_options(options)
59
+ full_options = DEFAULT_INSTALL_OPTIONS.dup.merge(options)
60
+ options_strings = []
61
+
62
+ jobs = full_options.delete("jobs")
63
+ if jobs > 1
64
+ if Utils.support_parallel_installation?
65
+ options_strings << "--jobs=#{jobs}"
66
+ else
67
+ warn("Your current version of Bundler does not support parallel installation. Please " \
68
+ "upgrade Bundler to version >= 1.4.0, or invoke `appraisal` without `--jobs` option.")
69
+ end
70
+ end
71
+
72
+ path = full_options.delete("path")
73
+ if path
74
+ relative_path = project_root.join(options["path"])
75
+ options_strings << "--path #{relative_path}"
76
+ end
77
+
78
+ full_options.each do |flag, val|
79
+ options_strings << "--#{flag} #{val}"
80
+ end
81
+
82
+ options_strings.join(" ") if options_strings != []
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bundler_adapter"
4
+ require_relative "ore_adapter"
5
+
6
+ module Appraisal
7
+ module GemManager
8
+ # Factory for creating gem manager adapters.
9
+ # Raises errors for unknown or unavailable gem managers.
10
+ class Factory
11
+ ADAPTERS = {
12
+ "bundler" => BundlerAdapter,
13
+ "ore" => OreAdapter,
14
+ }.freeze
15
+
16
+ DEFAULT_MANAGER = "bundler"
17
+
18
+ class << self
19
+ # Create a gem manager adapter
20
+ # @param gemfile_path [String] path to the gemfile
21
+ # @param project_root [Pathname] root directory of the project
22
+ # @param manager [String, nil] gem manager name (bundler or ore)
23
+ # @return [Base] gem manager adapter instance
24
+ # @raise [UnknownGemManagerError] if manager is not recognized
25
+ # @raise [OreNotAvailableError] if ore is requested but not installed
26
+ def create(gemfile_path, project_root, manager: nil)
27
+ manager_name = normalize_manager_name(manager)
28
+
29
+ adapter_class = ADAPTERS.fetch(manager_name) do
30
+ raise UnknownGemManagerError.new(manager_name, ADAPTERS.keys)
31
+ end
32
+
33
+ adapter = adapter_class.new(gemfile_path, project_root)
34
+ adapter.validate_availability!
35
+ adapter
36
+ end
37
+
38
+ # List of available gem manager names
39
+ # @return [Array<String>]
40
+ def available_managers
41
+ ADAPTERS.keys
42
+ end
43
+
44
+ private
45
+
46
+ def normalize_manager_name(manager)
47
+ return DEFAULT_MANAGER if manager.nil? || manager.to_s.strip.empty?
48
+
49
+ manager.to_s.strip.downcase
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Appraisal
6
+ module GemManager
7
+ # Ore-light adapter for gem management operations.
8
+ # Ore-light is a fast, Go-based alternative to Bundler for gem installation.
9
+ # See: https://github.com/contriboss/ore-light
10
+ class OreAdapter < Base
11
+ ORE_EXECUTABLE = "ore"
12
+
13
+ def name
14
+ "ore"
15
+ end
16
+
17
+ def available?
18
+ system("which #{ORE_EXECUTABLE} > /dev/null 2>&1")
19
+ end
20
+
21
+ def validate_availability!
22
+ raise OreNotAvailableError unless available?
23
+ end
24
+
25
+ def install(options = {})
26
+ validate_availability!
27
+
28
+ lockfile = "#{gemfile_path}.lock"
29
+
30
+ # Run ore lock first if lockfile doesn't exist
31
+ unless File.exist?(lockfile)
32
+ lock_command = [ORE_EXECUTABLE, "lock", "-gemfile", gemfile_path]
33
+ run_ore_command(lock_command)
34
+ end
35
+
36
+ command = install_command(options)
37
+ run_ore_command(command)
38
+ end
39
+
40
+ def update(gems = [])
41
+ validate_availability!
42
+
43
+ command = update_command(gems)
44
+ run_ore_command(command)
45
+ end
46
+
47
+ private
48
+
49
+ def install_command(options = {})
50
+ cmd = [ORE_EXECUTABLE, "install"]
51
+
52
+ # Map bundler options to ore options
53
+ # Ore uses -workers instead of --jobs
54
+ jobs = options["jobs"]
55
+ cmd << "-workers=#{jobs}" if jobs && jobs > 1
56
+
57
+ # Ore uses -without with comma-separated groups
58
+ without = options["without"]
59
+ if without && !without.empty?
60
+ # Convert space-separated to comma-separated
61
+ groups = without.split(/\s+/).join(",")
62
+ cmd << "-without=#{groups}"
63
+ end
64
+
65
+ # Ore uses -vendor instead of --path
66
+ path = options["path"]
67
+ if path
68
+ relative_path = project_root.join(path)
69
+ cmd << "-vendor=#{relative_path}"
70
+ end
71
+
72
+ # Ore uses -lockfile to specify the lockfile path
73
+ cmd << "-lockfile=#{gemfile_path}.lock"
74
+
75
+ # Note: Ore does not support --retry, so we ignore that option
76
+
77
+ cmd
78
+ end
79
+
80
+ def update_command(gems)
81
+ cmd = [ORE_EXECUTABLE, "update", "-gemfile", gemfile_path]
82
+ cmd.concat(gems) if gems.any?
83
+ cmd
84
+ end
85
+
86
+ def run_ore_command(command)
87
+ puts ">> BUNDLE_GEMFILE=#{gemfile_path} #{command.join(" ")}"
88
+
89
+ # Ore resolves path dependencies relative to the current working directory,
90
+ # not relative to the gemfile location. We need to cd to the gemfile's
91
+ # directory so that relative paths like "../appraisal2" resolve correctly.
92
+ gemfile_dir = File.dirname(gemfile_path)
93
+
94
+ Bundler.with_original_env do
95
+ ENV["BUNDLE_GEMFILE"] = gemfile_path
96
+ ENV["APPRAISAL_INITIALIZED"] = "1"
97
+ Dir.chdir(gemfile_dir) do
98
+ exit(1) unless Kernel.system(*command)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+ require_relative "gem_manager/factory"
5
+
6
+ module Appraisal
7
+ # Gem manager abstraction layer.
8
+ # Supports multiple gem managers (bundler, ore-light) via adapter pattern.
9
+ module GemManager
10
+ end
11
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appraisal
4
- VERSION = "3.0.0"
4
+ module Version
5
+ VERSION = "3.0.1"
6
+ end
7
+ VERSION = Version::VERSION # Traditional constant location
5
8
  end
data.tar.gz.sig CHANGED
Binary file