toys-core 0.10.1 → 0.11.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 +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +5 -5
- data/docs/guide.md +2 -0
- data/lib/toys-core.rb +3 -0
- data/lib/toys/compat.rb +13 -4
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/tool.rb +17 -0
- data/lib/toys/loader.rb +71 -31
- data/lib/toys/source_info.rb +22 -23
- data/lib/toys/standard_mixins/bundler.rb +121 -40
- data/lib/toys/standard_mixins/exec.rb +1 -1
- data/lib/toys/utils/exec.rb +88 -24
- data/lib/toys/utils/gems.rb +124 -30
- data/lib/toys/utils/help_text.rb +54 -59
- data/lib/toys/utils/terminal.rb +1 -1
- metadata +5 -131
@@ -7,32 +7,55 @@ module Toys
|
|
7
7
|
#
|
8
8
|
# The following parameters can be passed when including this mixin:
|
9
9
|
#
|
10
|
-
# * `:
|
10
|
+
# * `:static` (Boolean) If `true`, installs the bundle immediately, when
|
11
|
+
# defining the tool. If `false` (the default), installs the bundle just
|
12
|
+
# before the tool runs.
|
11
13
|
#
|
12
|
-
# * `:
|
13
|
-
#
|
14
|
+
# * `:groups` (Array\<String\>) The groups to include in setup.
|
15
|
+
#
|
16
|
+
# * `:gemfile_path` (String) The path to the Gemfile to use. If `nil` or
|
17
|
+
# not given, the `:search_dirs` will be searched for a Gemfile.
|
18
|
+
#
|
19
|
+
# * `:search_dirs` (String,Symbol,Array\<String,Symbol\>) Directories to
|
20
|
+
# search for a Gemfile.
|
14
21
|
#
|
15
22
|
# You can pass full directory paths, and/or any of the following:
|
16
|
-
# * `:context` - the current context directory
|
17
|
-
# * `:current` - the current working directory
|
18
|
-
# * `:toys` - the Toys directory containing the tool definition
|
23
|
+
# * `:context` - the current context directory.
|
24
|
+
# * `:current` - the current working directory.
|
25
|
+
# * `:toys` - the Toys directory containing the tool definition, and
|
26
|
+
# any of its parents within the Toys directory hierarchy.
|
19
27
|
#
|
20
28
|
# The default is to search `[:toys, :context, :current]` in that order.
|
29
|
+
# See {DEFAULT_SEARCH_DIRS}.
|
30
|
+
#
|
31
|
+
# For most directories, the bundler mixin will look for the files
|
32
|
+
# ".gems.rb", "gems.rb", and "Gemfile", in that order. In `:toys`
|
33
|
+
# directories, it will look only for ".gems.rb" and "Gemfile", in that
|
34
|
+
# order. These can be overridden by setting the `:gemfile_names` and/or
|
35
|
+
# `:toys_gemfile_names` arguments.
|
36
|
+
#
|
37
|
+
# * `:gemfile_names` (Array\<String\>) File names that are recognized as
|
38
|
+
# Gemfiles when searching in directories other than Toys directories.
|
39
|
+
# Defaults to {Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES}.
|
40
|
+
#
|
41
|
+
# * `:toys_gemfile_names` (Array\<String\>) File names that are
|
42
|
+
# recognized as Gemfiles when wearching in Toys directories.
|
43
|
+
# Defaults to {DEFAULT_TOYS_GEMFILE_NAMES}.
|
21
44
|
#
|
22
45
|
# * `:on_missing` (Symbol) What to do if a needed gem is not installed.
|
23
46
|
#
|
24
47
|
# Supported values:
|
25
|
-
# * `:confirm` - prompt the user on whether to install (default)
|
26
|
-
# * `:error` - raise an exception
|
27
|
-
# * `:install` - just install the gem
|
48
|
+
# * `:confirm` - prompt the user on whether to install (default).
|
49
|
+
# * `:error` - raise an exception.
|
50
|
+
# * `:install` - just install the gem.
|
28
51
|
#
|
29
52
|
# * `:on_conflict` (Symbol) What to do if bundler has already been run
|
30
53
|
# with a different Gemfile.
|
31
54
|
#
|
32
55
|
# Supported values:
|
33
|
-
# * `:error` - raise an exception (default)
|
34
|
-
# * `:ignore` - just silently proceed without bundling again
|
35
|
-
# * `:warn` - print a warning and proceed without bundling again
|
56
|
+
# * `:error` - raise an exception (default).
|
57
|
+
# * `:ignore` - just silently proceed without bundling again.
|
58
|
+
# * `:warn` - print a warning and proceed without bundling again.
|
36
59
|
#
|
37
60
|
# * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
|
38
61
|
# * `:input` (IO) Input IO (optional, defaults to STDIN)
|
@@ -41,48 +64,106 @@ module Toys
|
|
41
64
|
module Bundler
|
42
65
|
include Mixin
|
43
66
|
|
44
|
-
on_initialize do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
67
|
+
on_initialize do |static: false, **kwargs|
|
68
|
+
unless static
|
69
|
+
context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
|
70
|
+
source_info = self[::Toys::Context::Key::TOOL_SOURCE]
|
71
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
on_include do |static: false, **kwargs|
|
76
|
+
if static
|
77
|
+
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Default search directories for Gemfiles.
|
83
|
+
# @return [Array<String,Symbol>]
|
84
|
+
#
|
85
|
+
DEFAULT_SEARCH_DIRS = [:toys, :context, :current].freeze
|
86
|
+
|
87
|
+
##
|
88
|
+
# The gemfile names that are searched by default in Toys directories.
|
89
|
+
# @return [Array<String>]
|
90
|
+
#
|
91
|
+
DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
|
92
|
+
|
93
|
+
# @private
|
94
|
+
def self.setup_bundle(context_directory,
|
95
|
+
source_info,
|
96
|
+
gemfile_path: nil,
|
97
|
+
search_dirs: nil,
|
98
|
+
gemfile_names: nil,
|
99
|
+
toys_gemfile_names: nil,
|
100
|
+
groups: nil,
|
101
|
+
on_missing: nil,
|
102
|
+
on_conflict: nil,
|
103
|
+
terminal: nil,
|
104
|
+
input: nil,
|
105
|
+
output: nil)
|
52
106
|
require "toys/utils/gems"
|
53
|
-
|
107
|
+
gemfile_path ||= begin
|
108
|
+
gemfile_finder = GemfileFinder.new(context_directory, source_info,
|
109
|
+
gemfile_names, toys_gemfile_names)
|
110
|
+
gemfile_finder.search(search_dirs || DEFAULT_SEARCH_DIRS)
|
111
|
+
end
|
54
112
|
gems = ::Toys::Utils::Gems.new(on_missing: on_missing, on_conflict: on_conflict,
|
55
113
|
terminal: terminal, input: input, output: output)
|
56
|
-
gems.bundle(groups: groups,
|
114
|
+
gems.bundle(groups: groups, gemfile_path: gemfile_path)
|
57
115
|
end
|
58
116
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
117
|
+
# @private
|
118
|
+
class GemfileFinder
|
119
|
+
# @private
|
120
|
+
def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
|
121
|
+
@context_directory = context_directory
|
122
|
+
@source_info = source_info
|
123
|
+
@gemfile_names = gemfile_names
|
124
|
+
@toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
def search(search_dir)
|
129
|
+
case search_dir
|
130
|
+
when ::Array
|
131
|
+
search_array(search_dir)
|
132
|
+
when ::String
|
133
|
+
::Toys::Utils::Gems.find_gemfile(search_dir, gemfile_names: @gemfile_names)
|
64
134
|
when :context
|
65
|
-
|
135
|
+
search(@context_directory)
|
66
136
|
when :current
|
67
|
-
::Dir.getwd
|
137
|
+
search(::Dir.getwd)
|
68
138
|
when :toys
|
69
|
-
|
70
|
-
when ::String
|
71
|
-
dir
|
139
|
+
search_toys
|
72
140
|
else
|
73
141
|
raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
|
74
142
|
end
|
75
143
|
end
|
76
|
-
end
|
77
144
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
145
|
+
private
|
146
|
+
|
147
|
+
def search_array(search_dirs)
|
148
|
+
search_dirs.each do |search_dir|
|
149
|
+
result = search(search_dir)
|
150
|
+
return result if result
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def search_toys
|
156
|
+
source_info = @source_info
|
157
|
+
while source_info
|
158
|
+
if source_info.source_type == :directory
|
159
|
+
result = ::Toys::Utils::Gems.find_gemfile(source_info.source_path,
|
160
|
+
gemfile_names: @toys_gemfile_names)
|
161
|
+
return result if result
|
162
|
+
end
|
163
|
+
source_info = source_info.parent
|
164
|
+
end
|
165
|
+
nil
|
84
166
|
end
|
85
|
-
dirs
|
86
167
|
end
|
87
168
|
end
|
88
169
|
end
|
@@ -728,7 +728,7 @@ module Toys
|
|
728
728
|
def self._setup_clean_process(cmd)
|
729
729
|
raise ::ArgumentError, "Toys process is unknown" unless ::Toys.executable_path
|
730
730
|
cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
|
731
|
-
cmd =
|
731
|
+
cmd = [::RbConfig.ruby, "--disable=gems", ::Toys.executable_path] + cmd
|
732
732
|
if defined?(::Bundler)
|
733
733
|
if ::Bundler.respond_to?(:with_unbundled_env)
|
734
734
|
::Bundler.with_unbundled_env { yield(cmd) }
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -547,7 +547,7 @@ module Toys
|
|
547
547
|
##
|
548
548
|
# The process ID.
|
549
549
|
#
|
550
|
-
# Exactly one of
|
550
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
551
551
|
#
|
552
552
|
# @return [Integer] if the process start was successful
|
553
553
|
# @return [nil] if the process could not be started.
|
@@ -557,7 +557,7 @@ module Toys
|
|
557
557
|
##
|
558
558
|
# The exception raised when the process failed to start.
|
559
559
|
#
|
560
|
-
# Exactly one of
|
560
|
+
# Exactly one of {#exception} and {#pid} will be non-nil.
|
561
561
|
#
|
562
562
|
# @return [Exception] if the process failed to start.
|
563
563
|
# @return [nil] if the process start was successful.
|
@@ -722,15 +722,20 @@ module Toys
|
|
722
722
|
##
|
723
723
|
# Wait for the subcommand to complete, and return a result object.
|
724
724
|
#
|
725
|
+
# Closes the control streams if present. The stdin stream is always
|
726
|
+
# closed, even if the call times out. The stdout and stderr streams are
|
727
|
+
# closed only after the command terminates.
|
728
|
+
#
|
725
729
|
# @param timeout [Numeric,nil] The timeout in seconds, or `nil` to
|
726
730
|
# wait indefinitely.
|
727
731
|
# @return [Toys::Utils::Exec::Result] The result object
|
728
732
|
# @return [nil] if a timeout occurred.
|
729
733
|
#
|
730
734
|
def result(timeout: nil)
|
735
|
+
close_streams(:in)
|
731
736
|
return nil if @wait_thread && !@wait_thread.join(timeout)
|
732
737
|
@result ||= begin
|
733
|
-
close_streams
|
738
|
+
close_streams(:out)
|
734
739
|
@join_threads.each(&:join)
|
735
740
|
Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
|
736
741
|
.tap { |result| @result_callback&.call(result) }
|
@@ -738,13 +743,13 @@ module Toys
|
|
738
743
|
end
|
739
744
|
|
740
745
|
##
|
741
|
-
# Close
|
746
|
+
# Close the controller's streams.
|
742
747
|
# @private
|
743
748
|
#
|
744
|
-
def close_streams
|
745
|
-
@in.close if @in && !@in.closed?
|
746
|
-
@out.close if @out && !@out.closed?
|
747
|
-
@err.close if @err && !@err.closed?
|
749
|
+
def close_streams(which)
|
750
|
+
@in.close if which != :out && @in && !@in.closed?
|
751
|
+
@out.close if which != :in && @out && !@out.closed?
|
752
|
+
@err.close if which != :in && @err && !@err.closed?
|
748
753
|
self
|
749
754
|
end
|
750
755
|
|
@@ -773,7 +778,21 @@ module Toys
|
|
773
778
|
end
|
774
779
|
|
775
780
|
##
|
776
|
-
# The result returned from a subcommand execution.
|
781
|
+
# The result returned from a subcommand execution. This includes the
|
782
|
+
# identifying name of the execution (if any), the result status of the
|
783
|
+
# execution, and any captured stream output.
|
784
|
+
#
|
785
|
+
# Possible result statuses are:
|
786
|
+
#
|
787
|
+
# * The process failed to start. {Result#failed?} will return true, and
|
788
|
+
# {Result#exception} will return an exception describing the failure
|
789
|
+
# (often an errno).
|
790
|
+
# * The process executed and exited with a normal exit code. Either
|
791
|
+
# {Result#success?} or {Result#error?} will return true, and
|
792
|
+
# {Result.exit_code} will return the numeric exit code.
|
793
|
+
# * The process executed but was terminated by an uncaught signal.
|
794
|
+
# {Result#signaled?} will return true, and {Result#term_signal} will
|
795
|
+
# return the numeric signal code.
|
777
796
|
#
|
778
797
|
class Result
|
779
798
|
## @private
|
@@ -809,11 +828,13 @@ module Toys
|
|
809
828
|
attr_reader :captured_err
|
810
829
|
|
811
830
|
##
|
812
|
-
# The status
|
831
|
+
# The Ruby process status object, providing various information about
|
832
|
+
# the ending state of the process.
|
813
833
|
#
|
814
|
-
# Exactly one of
|
834
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
815
835
|
#
|
816
|
-
# @return [Process::Status] The status
|
836
|
+
# @return [Process::Status] The status, if the process was successfully
|
837
|
+
# spanwed and terminated.
|
817
838
|
# @return [nil] if the process could not be started.
|
818
839
|
#
|
819
840
|
attr_reader :status
|
@@ -821,7 +842,7 @@ module Toys
|
|
821
842
|
##
|
822
843
|
# The exception raised if a process couldn't be started.
|
823
844
|
#
|
824
|
-
# Exactly one of
|
845
|
+
# Exactly one of {#exception} and {#status} will be non-nil.
|
825
846
|
#
|
826
847
|
# @return [Exception] The exception raised from process start.
|
827
848
|
# @return [nil] if the process started successfully.
|
@@ -829,33 +850,76 @@ module Toys
|
|
829
850
|
attr_reader :exception
|
830
851
|
|
831
852
|
##
|
832
|
-
# The numeric status code
|
853
|
+
# The numeric status code for a process that exited normally,
|
833
854
|
#
|
834
|
-
#
|
835
|
-
#
|
855
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
856
|
+
# non-nil.
|
836
857
|
#
|
837
|
-
# @return [Integer]
|
858
|
+
# @return [Integer] the numeric status code, if the process started
|
859
|
+
# successfully and exited normally.
|
860
|
+
# @return [nil] if the process did not start successfully, or was
|
861
|
+
# terminated by an uncaught signal.
|
838
862
|
#
|
839
863
|
def exit_code
|
840
|
-
status
|
864
|
+
status&.exitstatus
|
865
|
+
end
|
866
|
+
|
867
|
+
##
|
868
|
+
# The numeric signal code that caused process termination.
|
869
|
+
#
|
870
|
+
# Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
|
871
|
+
# non-nil.
|
872
|
+
#
|
873
|
+
# @return [Integer] The signal that caused the process to terminate.
|
874
|
+
# @return [nil] if the process did not start successfully, or executed
|
875
|
+
# and exited with a normal exit code.
|
876
|
+
#
|
877
|
+
def term_signal
|
878
|
+
status&.termsig
|
879
|
+
end
|
880
|
+
|
881
|
+
##
|
882
|
+
# Returns true if the subprocess failed to start, or false if the
|
883
|
+
# process was able to execute.
|
884
|
+
#
|
885
|
+
# @return [Boolean]
|
886
|
+
#
|
887
|
+
def failed?
|
888
|
+
status.nil?
|
889
|
+
end
|
890
|
+
|
891
|
+
##
|
892
|
+
# Returns true if the subprocess terminated due to an unhandled signal,
|
893
|
+
# or false if the process failed to start or exited normally.
|
894
|
+
#
|
895
|
+
# @return [Boolean]
|
896
|
+
#
|
897
|
+
def signaled?
|
898
|
+
!term_signal.nil?
|
841
899
|
end
|
842
900
|
|
843
901
|
##
|
844
|
-
# Returns true if the subprocess terminated with a zero status
|
902
|
+
# Returns true if the subprocess terminated with a zero status, or
|
903
|
+
# false if the process failed to start, terminated due to a signal, or
|
904
|
+
# returned a nonzero status.
|
845
905
|
#
|
846
906
|
# @return [Boolean]
|
847
907
|
#
|
848
908
|
def success?
|
849
|
-
exit_code
|
909
|
+
code = exit_code
|
910
|
+
!code.nil? && code.zero?
|
850
911
|
end
|
851
912
|
|
852
913
|
##
|
853
|
-
# Returns true if the subprocess terminated with a nonzero status
|
914
|
+
# Returns true if the subprocess terminated with a nonzero status, or
|
915
|
+
# false if the process failed to start, terminated due to a signal, or
|
916
|
+
# returned a zero status.
|
854
917
|
#
|
855
918
|
# @return [Boolean]
|
856
919
|
#
|
857
920
|
def error?
|
858
|
-
|
921
|
+
code = exit_code
|
922
|
+
!code.nil? && !code.zero?
|
859
923
|
end
|
860
924
|
end
|
861
925
|
|
@@ -887,10 +951,10 @@ module Toys
|
|
887
951
|
return controller if @config_opts[:background]
|
888
952
|
begin
|
889
953
|
@block&.call(controller)
|
954
|
+
controller.result
|
890
955
|
ensure
|
891
|
-
controller.close_streams
|
956
|
+
controller.close_streams(:both)
|
892
957
|
end
|
893
|
-
controller.result
|
894
958
|
end
|
895
959
|
|
896
960
|
private
|
data/lib/toys/utils/gems.rb
CHANGED
@@ -64,6 +64,19 @@ module Toys
|
|
64
64
|
class AlreadyBundledError < BundlerFailedError
|
65
65
|
end
|
66
66
|
|
67
|
+
##
|
68
|
+
# The bundle contained a toys or toys-core dependency that is
|
69
|
+
# incompatible with the currently running version.
|
70
|
+
#
|
71
|
+
class IncompatibleToysError < BundlerFailedError
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# The gemfile names that are searched by default.
|
76
|
+
# @return [Array<String>]
|
77
|
+
#
|
78
|
+
DEFAULT_GEMFILE_NAMES = [".gems.rb", "gems.rb", "Gemfile"].freeze
|
79
|
+
|
67
80
|
##
|
68
81
|
# Activate the given gem. If it is not present, attempt to install it (or
|
69
82
|
# inform the user to update the bundle).
|
@@ -81,16 +94,22 @@ module Toys
|
|
81
94
|
#
|
82
95
|
# @param on_missing [:confirm,:error,:install] What to do if a needed gem
|
83
96
|
# is not installed. Possible values:
|
97
|
+
#
|
84
98
|
# * `:confirm` - prompt the user on whether to install
|
85
99
|
# * `:error` - raise an exception
|
86
100
|
# * `:install` - just install the gem
|
101
|
+
#
|
87
102
|
# The default is `:confirm`.
|
103
|
+
#
|
88
104
|
# @param on_conflict [:error,:warn,:ignore] What to do if bundler has
|
89
105
|
# already been run with a different Gemfile. Possible values:
|
106
|
+
#
|
90
107
|
# * `:error` - raise an exception
|
91
108
|
# * `:ignore` - just silently proceed without bundling again
|
92
109
|
# * `:warn` - print a warning and proceed without bundling again
|
110
|
+
#
|
93
111
|
# The default is `:error`.
|
112
|
+
#
|
94
113
|
# @param terminal [Toys::Utils::Terminal] Terminal to use (optional)
|
95
114
|
# @param input [IO] Input IO (optional, defaults to STDIN)
|
96
115
|
# @param output [IO] Output IO (optional, defaults to STDOUT)
|
@@ -136,26 +155,54 @@ module Toys
|
|
136
155
|
end
|
137
156
|
|
138
157
|
##
|
139
|
-
#
|
158
|
+
# Search for an appropriate Gemfile, and set up the bundle.
|
159
|
+
#
|
160
|
+
# @param groups [Array<String>] The groups to include in setup.
|
161
|
+
#
|
162
|
+
# @param gemfile_path [String] The path to the Gemfile to use. If `nil`
|
163
|
+
# or not given, the `:search_dirs` will be searched for a Gemfile.
|
164
|
+
#
|
165
|
+
# @param search_dirs [String,Array<String>] Directories in which to
|
166
|
+
# search for a Gemfile, if gemfile_path is not given. You can provide
|
167
|
+
# a single directory or an array of directories.
|
168
|
+
#
|
169
|
+
# @param gemfile_names [String,Array<String>] File names that are
|
170
|
+
# recognized as Gemfiles, when searching because gemfile_path is not
|
171
|
+
# given. Defaults to {DEFAULT_GEMFILE_NAMES}.
|
140
172
|
#
|
141
|
-
# @param groups [Array<String>] The groups to include in setup
|
142
|
-
# @param search_dirs [Array<String>] Directories to search for a Gemfile
|
143
173
|
# @return [void]
|
144
174
|
#
|
145
175
|
def bundle(groups: nil,
|
146
|
-
|
176
|
+
gemfile_path: nil,
|
177
|
+
search_dirs: nil,
|
178
|
+
gemfile_names: nil)
|
179
|
+
Array(search_dirs).each do |dir|
|
180
|
+
break if gemfile_path
|
181
|
+
gemfile_path = Gems.find_gemfile(dir, gemfile_names: gemfile_names)
|
182
|
+
end
|
183
|
+
raise GemfileNotFoundError, "Gemfile not found" unless gemfile_path
|
147
184
|
Gems.synchronize do
|
148
|
-
gemfile_path = find_gemfile(Array(search_dirs))
|
149
|
-
activate("bundler", "~> 2.1")
|
150
185
|
if configure_gemfile(gemfile_path)
|
186
|
+
activate("bundler", "~> 2.1")
|
187
|
+
require "bundler"
|
151
188
|
setup_bundle(gemfile_path, groups || [])
|
152
189
|
end
|
153
190
|
end
|
154
191
|
end
|
155
192
|
|
193
|
+
# @private
|
194
|
+
def self.find_gemfile(search_dir, gemfile_names: nil)
|
195
|
+
gemfile_names ||= DEFAULT_GEMFILE_NAMES
|
196
|
+
Array(gemfile_names).each do |file|
|
197
|
+
gemfile_path = ::File.join(search_dir, file)
|
198
|
+
return gemfile_path if ::File.readable?(gemfile_path)
|
199
|
+
end
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
156
203
|
@global_mutex = ::Monitor.new
|
157
204
|
|
158
|
-
|
205
|
+
# @private
|
159
206
|
def self.synchronize(&block)
|
160
207
|
@global_mutex.synchronize(&block)
|
161
208
|
end
|
@@ -184,14 +231,14 @@ module Toys
|
|
184
231
|
error.message.include?("Could not find")
|
185
232
|
end
|
186
233
|
if !is_missing_spec || @on_missing == :error
|
187
|
-
|
234
|
+
report_activation_error(name, requirements, error)
|
188
235
|
return
|
189
236
|
end
|
190
237
|
confirm_and_install_gem(name, requirements)
|
191
238
|
begin
|
192
239
|
gem(name, *requirements)
|
193
240
|
rescue ::Gem::LoadError => e
|
194
|
-
|
241
|
+
report_activation_error(name, requirements, e)
|
195
242
|
end
|
196
243
|
end
|
197
244
|
|
@@ -215,7 +262,7 @@ module Toys
|
|
215
262
|
::Gem::Specification.reset
|
216
263
|
end
|
217
264
|
|
218
|
-
def
|
265
|
+
def report_activation_error(name, requirements, err)
|
219
266
|
if ::ENV["BUNDLE_GEMFILE"]
|
220
267
|
raise GemfileUpdateNeededError.new(gem_requirements_text(name, requirements),
|
221
268
|
::ENV["BUNDLE_GEMFILE"])
|
@@ -223,22 +270,16 @@ module Toys
|
|
223
270
|
raise ActivationFailedError, err.message
|
224
271
|
end
|
225
272
|
|
226
|
-
def find_gemfile(search_dirs)
|
227
|
-
search_dirs.each do |dir|
|
228
|
-
gemfile_path = ::File.join(dir, "Gemfile")
|
229
|
-
return gemfile_path if ::File.readable?(gemfile_path)
|
230
|
-
end
|
231
|
-
raise GemfileNotFoundError, "Gemfile not found"
|
232
|
-
end
|
233
|
-
|
234
273
|
def configure_gemfile(gemfile_path)
|
235
274
|
old_path = ::ENV["BUNDLE_GEMFILE"]
|
236
|
-
if old_path
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
275
|
+
if old_path
|
276
|
+
if gemfile_path != old_path
|
277
|
+
case @on_conflict
|
278
|
+
when :warn
|
279
|
+
terminal.puts("Warning: could not set up bundler because it is already set up.", :red)
|
280
|
+
when :error
|
281
|
+
raise AlreadyBundledError, "Could not set up bundler because it is already set up"
|
282
|
+
end
|
242
283
|
end
|
243
284
|
return false
|
244
285
|
end
|
@@ -247,13 +288,60 @@ module Toys
|
|
247
288
|
end
|
248
289
|
|
249
290
|
def setup_bundle(gemfile_path, groups)
|
250
|
-
require "bundler"
|
251
291
|
begin
|
252
|
-
|
253
|
-
|
292
|
+
modify_bundle_definition(gemfile_path)
|
293
|
+
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
294
|
+
rescue ::Bundler::GemNotFound, ::Bundler::VersionConflict
|
295
|
+
restore_toys_libs
|
254
296
|
install_bundle(gemfile_path)
|
255
297
|
::Bundler.reset!
|
256
|
-
|
298
|
+
modify_bundle_definition(gemfile_path)
|
299
|
+
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
300
|
+
end
|
301
|
+
restore_toys_libs
|
302
|
+
end
|
303
|
+
|
304
|
+
def modify_bundle_definition(gemfile_path)
|
305
|
+
builder = ::Bundler::Dsl.new
|
306
|
+
builder.eval_gemfile(gemfile_path)
|
307
|
+
toys_gems = ["toys-core"]
|
308
|
+
remove_gem_from_definition(builder, "toys-core")
|
309
|
+
removed_toys = remove_gem_from_definition(builder, "toys")
|
310
|
+
add_gem_to_definition(builder, "toys-core")
|
311
|
+
if removed_toys || ::Toys.const_defined?(:VERSION)
|
312
|
+
add_gem_to_definition(builder, "toys")
|
313
|
+
toys_gems << "toys"
|
314
|
+
end
|
315
|
+
definition = builder.to_definition(gemfile_path + ".lock", { gems: toys_gems })
|
316
|
+
::Bundler.instance_variable_set(:@definition, definition)
|
317
|
+
end
|
318
|
+
|
319
|
+
def remove_gem_from_definition(builder, name)
|
320
|
+
existing_dep = builder.dependencies.find { |dep| dep.name == name }
|
321
|
+
return false unless existing_dep
|
322
|
+
unless existing_dep.requirement.satisfied_by?(::Gem::Version.new(::Toys::Core::VERSION))
|
323
|
+
raise IncompatibleToysError,
|
324
|
+
"The bundle lists #{name} #{existing_dep.requirement} as a dependency, which is" \
|
325
|
+
" incompatible with the current version #{::Toys::Core::VERSION}."
|
326
|
+
end
|
327
|
+
builder.dependencies.delete(existing_dep)
|
328
|
+
true
|
329
|
+
end
|
330
|
+
|
331
|
+
def add_gem_to_definition(builder, name)
|
332
|
+
if ::ENV["TOYS_DEV"] == "true"
|
333
|
+
path = ::File.join(::File.dirname(::File.dirname(::Toys::CORE_LIB_PATH)), name)
|
334
|
+
end
|
335
|
+
command = "gem #{name.inspect}, #{::Toys::Core::VERSION.inspect}, path: #{path.inspect}\n"
|
336
|
+
builder.eval_gemfile("current #{name}", command)
|
337
|
+
end
|
338
|
+
|
339
|
+
def restore_toys_libs
|
340
|
+
$LOAD_PATH.delete(::Toys::CORE_LIB_PATH)
|
341
|
+
$LOAD_PATH.unshift(::Toys::CORE_LIB_PATH)
|
342
|
+
if ::Toys.const_defined?(:LIB_PATH)
|
343
|
+
$LOAD_PATH.delete(::Toys::LIB_PATH)
|
344
|
+
$LOAD_PATH.unshift(::Toys::LIB_PATH)
|
257
345
|
end
|
258
346
|
end
|
259
347
|
|
@@ -264,7 +352,8 @@ module Toys
|
|
264
352
|
when :error
|
265
353
|
false
|
266
354
|
else
|
267
|
-
terminal.confirm("Your bundle
|
355
|
+
terminal.confirm("Your bundle requires additional gems. Install? ",
|
356
|
+
default: @default_confirm)
|
268
357
|
end
|
269
358
|
end
|
270
359
|
|
@@ -276,7 +365,12 @@ module Toys
|
|
276
365
|
" `cd #{gemfile_dir} && bundle install`"
|
277
366
|
end
|
278
367
|
require "bundler/cli"
|
279
|
-
|
368
|
+
begin
|
369
|
+
::Bundler::CLI.start(["install"])
|
370
|
+
rescue ::Bundler::GemNotFound, ::Bundler::InstallError
|
371
|
+
terminal.puts("Failed to install. Trying update...")
|
372
|
+
::Bundler::CLI.start(["update"])
|
373
|
+
end
|
280
374
|
end
|
281
375
|
end
|
282
376
|
end
|