appraisal2 3.0.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.
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ module Appraisal
6
+ # Executes commands with a clean environment
7
+ class Command
8
+ attr_reader :command, :env, :gemfile
9
+
10
+ def initialize(command, options = {})
11
+ @gemfile = options[:gemfile]
12
+ @env = options.fetch(:env, {})
13
+ @command = command_starting_with_bundle(command)
14
+ end
15
+
16
+ def run
17
+ run_env = test_environment.merge(env)
18
+
19
+ Bundler.with_original_env do
20
+ ensure_bundler_is_available
21
+ announce
22
+
23
+ ENV["BUNDLE_GEMFILE"] = gemfile
24
+ ENV["APPRAISAL_INITIALIZED"] = "1"
25
+ run_env.each_pair do |key, value|
26
+ ENV[key] = value
27
+ end
28
+
29
+ exit(1) unless Kernel.system(command_as_string)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def ensure_bundler_is_available
36
+ version = Utils.bundler_version
37
+ return if system(%(gem list --silent -i bundler -v #{version}))
38
+
39
+ puts ">> Reinstall Bundler into #{ENV["GEM_HOME"]}"
40
+
41
+ return if system("gem install bundler --version #{version}")
42
+
43
+ puts
44
+ puts <<-ERROR.rstrip
45
+ Bundler installation failed.
46
+ Please try running:
47
+ `GEM_HOME="#{ENV["GEM_HOME"]}" gem install bundler --version #{version}`
48
+ manually.
49
+ ERROR
50
+ exit(1)
51
+ end
52
+
53
+ def announce
54
+ if gemfile
55
+ puts ">> BUNDLE_GEMFILE=#{gemfile} #{command_as_string}"
56
+ else
57
+ puts ">> #{command_as_string}"
58
+ end
59
+ end
60
+
61
+ def command_starts_with_bundle?(original_command)
62
+ if original_command.is_a?(Array)
63
+ original_command.first =~ /^bundle/
64
+ else
65
+ original_command =~ /^bundle/
66
+ end
67
+ end
68
+
69
+ def command_starting_with_bundle(original_command)
70
+ if command_starts_with_bundle?(original_command)
71
+ original_command
72
+ else
73
+ %w[bundle exec] + original_command
74
+ end
75
+ end
76
+
77
+ def command_as_string
78
+ if command.is_a?(Array)
79
+ Shellwords.join(command)
80
+ else
81
+ command
82
+ end
83
+ end
84
+
85
+ def test_environment
86
+ return {} unless ENV["APPRAISAL_UNDER_TEST"] == "1"
87
+
88
+ {
89
+ "GEM_HOME" => ENV["GEM_HOME"],
90
+ "GEM_PATH" => "",
91
+ }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Conditional < BundlerDSL
8
+ def initialize(condition)
9
+ super()
10
+ @condition = condition
11
+ end
12
+
13
+ def to_s
14
+ <<-OUTPUT.rstrip
15
+ install_if #{@condition} do
16
+ #{indent(super)}
17
+ end
18
+ OUTPUT
19
+ end
20
+
21
+ # :nodoc:
22
+ def for_dup
23
+ return unless @condition.is_a?(String)
24
+
25
+ <<-OUTPUT.rstrip
26
+ install_if #{@condition} do
27
+ #{indent(super)}
28
+ end
29
+ OUTPUT
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appraisal
4
+ class Customize
5
+ def initialize(options = {})
6
+ heading = options.fetch(:heading, nil)
7
+ single_quotes = options.fetch(:single_quotes, false)
8
+ @@heading = !heading.nil? && heading.chomp
9
+ @@single_quotes = single_quotes
10
+ end
11
+
12
+ def self.heading(gemfile = nil)
13
+ @@heading ||= nil
14
+ return @@heading unless gemfile
15
+
16
+ customize(@@heading, gemfile)
17
+ end
18
+
19
+ def self.single_quotes
20
+ @@single_quotes ||= false
21
+ end
22
+
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
37
+
38
+ private_class_method :customize
39
+ end
40
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/utils"
4
+
5
+ module Appraisal
6
+ # Dependency on a gem and optional version requirements
7
+ class Dependency
8
+ attr_accessor :requirements
9
+ attr_reader :name
10
+
11
+ def initialize(name, requirements)
12
+ @name = name
13
+ @requirements = requirements
14
+ end
15
+
16
+ def to_s
17
+ formatted_output(Utils.format_arguments(path_prefixed_requirements))
18
+ end
19
+
20
+ # :nodoc:
21
+ def for_dup
22
+ formatted_output(Utils.format_arguments(requirements))
23
+ end
24
+
25
+ private
26
+
27
+ def path_prefixed_requirements
28
+ requirements.map do |requirement|
29
+ if requirement.is_a?(Hash)
30
+ requirement[:path] = Utils.prefix_path(requirement[:path]) if requirement[:path]
31
+
32
+ requirement[:git] = Utils.prefix_path(requirement[:git]) if requirement[:git]
33
+ end
34
+
35
+ requirement
36
+ end
37
+ end
38
+
39
+ def formatted_output(output_requirements)
40
+ [gem_name, output_requirements].compact.join(", ")
41
+ end
42
+
43
+ def gem_name
44
+ %(gem "#{name}")
45
+ end
46
+
47
+ def no_requirements?
48
+ requirements.nil? || requirements.empty?
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Std libs
4
+ require "set"
5
+
6
+ # This gem
7
+ require "appraisal/dependency"
8
+ require "appraisal/ordered_hash"
9
+
10
+ module Appraisal
11
+ class DependencyList
12
+ def initialize
13
+ @dependencies = OrderedHash.new
14
+ @removed_dependencies = Set.new
15
+ end
16
+
17
+ def add(name, requirements)
18
+ return if @removed_dependencies.include?(name)
19
+
20
+ @dependencies[name] = Dependency.new(name, requirements)
21
+ end
22
+
23
+ def remove(name)
24
+ return unless @removed_dependencies.add?(name)
25
+
26
+ @dependencies.delete(name)
27
+ end
28
+
29
+ def to_s
30
+ @dependencies.values.map(&:to_s).join("\n")
31
+ end
32
+
33
+ # :nodoc:
34
+ def for_dup
35
+ @dependencies.values.map(&:for_dup).join("\n")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appraisal
4
+ # Raises when Appraisal is unable to locate Appraisals file in the current directory.
5
+ class AppraisalsNotFound < StandardError
6
+ def message
7
+ "Unable to locate 'Appraisals' file in the current directory."
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+
5
+ module Appraisal
6
+ autoload :Gemspec, "appraisal/gemspec"
7
+ autoload :Git, "appraisal/git"
8
+ autoload :Group, "appraisal/group"
9
+ autoload :Path, "appraisal/path"
10
+ autoload :Platform, "appraisal/platform"
11
+ autoload :Source, "appraisal/source"
12
+ autoload :Conditional, "appraisal/conditional"
13
+
14
+ # Load bundler Gemfiles and merge dependencies
15
+ class Gemfile < BundlerDSL
16
+ def load(path)
17
+ run(File.read(path), path) if File.exist?(path)
18
+ end
19
+
20
+ def run(definitions, path, line = 1)
21
+ instance_eval(definitions, path, line) if definitions
22
+ end
23
+
24
+ def dup
25
+ Gemfile.new.tap do |gemfile|
26
+ gemfile.git_sources = @git_sources
27
+ gemfile.run(for_dup, __FILE__, __LINE__)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/utils"
4
+
5
+ module Appraisal
6
+ class Gemspec
7
+ attr_reader :options
8
+
9
+ def initialize(options = {})
10
+ @options = options
11
+ @options[:path] ||= "."
12
+ end
13
+
14
+ def to_s
15
+ "gemspec #{Utils.format_string(exported_options)}"
16
+ end
17
+
18
+ # :nodoc:
19
+ def for_dup
20
+ "gemspec #{Utils.format_string(@options)}"
21
+ end
22
+
23
+ private
24
+
25
+ def exported_options
26
+ @options.merge(
27
+ :path => Utils.prefix_path(@options[:path]),
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Git < BundlerDSL
8
+ def initialize(source, options = {})
9
+ super()
10
+ @source = source
11
+ @options = options
12
+ end
13
+
14
+ def to_s
15
+ if @options.empty?
16
+ <<-OUTPUT.rstrip
17
+ git #{Utils.prefix_path(@source).inspect} do
18
+ #{indent(super)}
19
+ end
20
+ OUTPUT
21
+ else
22
+ <<-OUTPUT.rstrip
23
+ git #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do
24
+ #{indent(super)}
25
+ end
26
+ OUTPUT
27
+ end
28
+ end
29
+
30
+ # :nodoc:
31
+ def for_dup
32
+ if @options.empty?
33
+ <<-OUTPUT.rstrip
34
+ git #{@source.inspect} do
35
+ #{indent(super)}
36
+ end
37
+ OUTPUT
38
+ else
39
+ <<-OUTPUT.rstrip
40
+ git #{@source.inspect}, #{Utils.format_string(@options)} do
41
+ #{indent(super)}
42
+ end
43
+ OUTPUT
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Group < BundlerDSL
8
+ def initialize(group_names)
9
+ super()
10
+ @group_names = group_names
11
+ end
12
+
13
+ def to_s
14
+ formatted_output(super)
15
+ end
16
+
17
+ # :nodoc:
18
+ def for_dup
19
+ formatted_output(super)
20
+ end
21
+
22
+ private
23
+
24
+ def formatted_output(output_dependencies)
25
+ <<-OUTPUT.rstrip
26
+ group #{Utils.format_arguments(@group_names)} do
27
+ #{indent(output_dependencies)}
28
+ end
29
+ OUTPUT
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ module Appraisal
2
+ # An ordered hash implementation for Ruby 1.8.7 compatibility. This is not
3
+ # a complete implementation, but it covers Appraisal's specific needs.
4
+ class OrderedHash < ::Hash
5
+ # Hashes are ordered in Ruby 1.9.
6
+ if RUBY_VERSION < "1.9"
7
+ def initialize(*args, &block)
8
+ super
9
+ @keys = []
10
+ end
11
+
12
+ def []=(key, value)
13
+ @keys << key unless has_key?(key)
14
+ super
15
+ end
16
+
17
+ def values
18
+ @keys.collect { |key| self[key] }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Path < BundlerDSL
8
+ def initialize(source, options = {})
9
+ super()
10
+ @source = source
11
+ @options = options
12
+ end
13
+
14
+ def to_s
15
+ if @options.empty?
16
+ <<-OUTPUT.rstrip
17
+ path #{Utils.prefix_path(@source).inspect} do
18
+ #{indent(super)}
19
+ end
20
+ OUTPUT
21
+ else
22
+ <<-OUTPUT.rstrip
23
+ path #{Utils.prefix_path(@source).inspect}, #{Utils.format_string(@options)} do
24
+ #{indent(super)}
25
+ end"
26
+ OUTPUT
27
+ end
28
+ end
29
+
30
+ # :nodoc:
31
+ def for_dup
32
+ if @options.empty?
33
+ <<-OUTPUT.rstrip
34
+ path #{@source.inspect} do
35
+ #{indent(super)}
36
+ end
37
+ OUTPUT
38
+ else
39
+ <<-OUTPUT.rstrip
40
+ path #{@source.inspect}, #{Utils.format_string(@options)} do
41
+ #{indent(super)}
42
+ end
43
+ OUTPUT
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Platform < BundlerDSL
8
+ def initialize(platform_names)
9
+ super()
10
+ @platform_names = platform_names
11
+ end
12
+
13
+ def to_s
14
+ formatted_output(super)
15
+ end
16
+
17
+ # :nodoc:
18
+ def for_dup
19
+ formatted_output(super)
20
+ end
21
+
22
+ private
23
+
24
+ def formatted_output(output_dependencies)
25
+ <<-OUTPUT.rstrip
26
+ platforms #{Utils.format_arguments(@platform_names)} do
27
+ #{indent(output_dependencies)}
28
+ end
29
+ OUTPUT
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/bundler_dsl"
4
+ require "appraisal/utils"
5
+
6
+ module Appraisal
7
+ class Source < BundlerDSL
8
+ def initialize(source)
9
+ super()
10
+ @source = source
11
+ end
12
+
13
+ def to_s
14
+ formatted_output(super)
15
+ end
16
+
17
+ # :nodoc:
18
+ def for_dup
19
+ formatted_output(super)
20
+ end
21
+
22
+ private
23
+
24
+ def formatted_output(output_dependencies)
25
+ <<-OUTPUT.rstrip
26
+ source #{@source.inspect} do
27
+ #{indent(output_dependencies)}
28
+ end
29
+ OUTPUT
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/appraisal_file"
4
+ require "rake/tasklib"
5
+
6
+ module Appraisal
7
+ # Defines tasks for installing appraisal dependencies and running other tasks
8
+ # for a given appraisal.
9
+ class Task < Rake::TaskLib
10
+ def initialize
11
+ namespace(:appraisal) do
12
+ desc("DEPRECATED: Generate a Gemfile for each appraisal")
13
+ task(:gemfiles) do
14
+ warn("`rake appraisal:gemfile` task is deprecated and will be removed soon. " \
15
+ "Please use `appraisal generate`.")
16
+ exec("bundle exec appraisal generate")
17
+ end
18
+
19
+ desc("DEPRECATED: Resolve and install dependencies for each appraisal")
20
+ task(:install) do
21
+ warn("`rake appraisal:install` task is deprecated and will be removed soon. " \
22
+ "Please use `appraisal install`.")
23
+ exec("bundle exec appraisal install")
24
+ end
25
+
26
+ desc("DEPRECATED: Remove all generated gemfiles from gemfiles/ folder")
27
+ task(:cleanup) do
28
+ warn("`rake appraisal:cleanup` task is deprecated and will be removed soon. " \
29
+ "Please use `appraisal clean`.")
30
+ exec("bundle exec appraisal clean")
31
+ end
32
+
33
+ begin
34
+ AppraisalFile.each do |appraisal|
35
+ desc("DEPRECATED: Run the given task for appraisal #{appraisal.name}")
36
+ task(appraisal.name) do
37
+ ARGV.shift
38
+ warn("`rake appraisal:#{appraisal.name}` task is deprecated and will be removed soon. " \
39
+ "Please use `appraisal #{appraisal.name} rake #{ARGV.join(" ")}`.")
40
+ exec("bundle exec appraisal #{appraisal.name} rake #{ARGV.join(" ")}")
41
+ end
42
+ end
43
+ rescue AppraisalsNotFound
44
+ end
45
+
46
+ task(:all) do
47
+ ARGV.shift
48
+ exec("bundle exec appraisal rake #{ARGV.join(" ")}")
49
+ end
50
+ end
51
+
52
+ desc("Run the given task for all appraisals")
53
+ task(:appraisal => "appraisal:all")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appraisal
4
+ # Contains methods for various operations
5
+ module Utils
6
+ class << self
7
+ def support_parallel_installation?
8
+ Gem::Version.create(Bundler::VERSION) >= Gem::Version.create("1.4.0.pre.1")
9
+ end
10
+
11
+ def support_git_local_installation?
12
+ Gem::Version.create(Bundler::VERSION) > Gem::Version.create("2.4.22")
13
+ end
14
+
15
+ # Appraisal needs to print Gemfiles in the oldest Ruby syntax that is supported by Appraisal.
16
+ # Otherwise, a project would not be able to use Appraisal to test compatibility
17
+ # with older versions of Ruby, which is a core use case for Appraisal.
18
+ def format_string(object, enclosing_object = false)
19
+ case object
20
+ when Hash
21
+ items = object.map do |key, value|
22
+ format_hash_value(key, value)
23
+ end
24
+
25
+ if enclosing_object
26
+ "{ #{items.join(", ")} }"
27
+ else
28
+ items.join(", ")
29
+ end
30
+ else
31
+ object.inspect
32
+ end
33
+ end
34
+
35
+ # Appraisal needs to print Gemfiles in the oldest Ruby syntax that is supported by Appraisal.
36
+ # This means formatting Hashes as Rockets, until support for Ruby 1.8 is dropped.
37
+ # Regardless of what Ruby is used to generate appraisals,
38
+ # generated appraisals may need to run on a different Ruby version.
39
+ # Generated appraisals should use a syntax compliant with the oldest supported Ruby version.
40
+ def format_hash_value(key, value)
41
+ key = format_string(key, true)
42
+ value = format_string(value, true)
43
+
44
+ "#{key} => #{value}"
45
+ end
46
+
47
+ def format_arguments(arguments)
48
+ return if arguments.empty?
49
+
50
+ arguments.map { |object| format_string(object, false) }.join(", ")
51
+ end
52
+
53
+ def join_parts(parts)
54
+ parts.reject(&:nil?).reject(&:empty?).join("\n\n").rstrip
55
+ end
56
+
57
+ def prefix_path(path)
58
+ if path !~ /^(?:\/|\S:)/ && path !~ /^\S+:\/\// && path !~ /^\S+@\S+:/
59
+ cleaned_path = path.gsub(/(^|\/)\.(?:\/|$)/, "\\1")
60
+ File.join("..", cleaned_path)
61
+ else
62
+ path
63
+ end
64
+ end
65
+
66
+ def bundler_version
67
+ Gem::Specification.detect { |spec| spec.name == "bundler" }.version.to_s
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appraisal
4
+ VERSION = "3.0.0"
5
+ end
data/lib/appraisal.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ require "appraisal2"
5
+ # :nocov:
data/lib/appraisal2.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal/version"
4
+ require "appraisal/task"
5
+
6
+ Appraisal::Task.new