jwhitmire-geminstaller 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/COPYING +1 -0
  2. data/History.txt +82 -0
  3. data/LICENSE +21 -0
  4. data/Manifest.txt +78 -0
  5. data/README.txt +77 -0
  6. data/Rakefile +210 -0
  7. data/TODO.txt +138 -0
  8. data/authors +2 -0
  9. data/bin/geminstaller +30 -0
  10. data/cruise_config.rb +23 -0
  11. data/focused_spec.sh +2 -0
  12. data/focused_spec_debug.sh +2 -0
  13. data/geminstaller.yml +47 -0
  14. data/lib/geminstaller/application.rb +112 -0
  15. data/lib/geminstaller/arg_parser.rb +177 -0
  16. data/lib/geminstaller/autogem.rb +48 -0
  17. data/lib/geminstaller/backward_compatibility.rb +17 -0
  18. data/lib/geminstaller/bundler_exporter.rb +19 -0
  19. data/lib/geminstaller/config.rb +68 -0
  20. data/lib/geminstaller/config_builder.rb +65 -0
  21. data/lib/geminstaller/enhanced_stream_ui.rb +71 -0
  22. data/lib/geminstaller/exact_match_list_command.rb +16 -0
  23. data/lib/geminstaller/file_reader.rb +31 -0
  24. data/lib/geminstaller/gem_arg_processor.rb +44 -0
  25. data/lib/geminstaller/gem_command_manager.rb +71 -0
  26. data/lib/geminstaller/gem_interaction_handler.rb +41 -0
  27. data/lib/geminstaller/gem_list_checker.rb +55 -0
  28. data/lib/geminstaller/gem_runner_proxy.rb +65 -0
  29. data/lib/geminstaller/gem_source_index_proxy.rb +21 -0
  30. data/lib/geminstaller/gem_spec_manager.rb +53 -0
  31. data/lib/geminstaller/geminstaller_access_error.rb +4 -0
  32. data/lib/geminstaller/geminstaller_error.rb +13 -0
  33. data/lib/geminstaller/hoe_extensions.rb +9 -0
  34. data/lib/geminstaller/install_processor.rb +71 -0
  35. data/lib/geminstaller/missing_dependency_finder.rb +46 -0
  36. data/lib/geminstaller/missing_file_error.rb +4 -0
  37. data/lib/geminstaller/noninteractive_chooser.rb +114 -0
  38. data/lib/geminstaller/outdated_gem_finder.rb +46 -0
  39. data/lib/geminstaller/output_filter.rb +49 -0
  40. data/lib/geminstaller/output_listener.rb +33 -0
  41. data/lib/geminstaller/output_observer.rb +36 -0
  42. data/lib/geminstaller/output_proxy.rb +36 -0
  43. data/lib/geminstaller/registry.rb +137 -0
  44. data/lib/geminstaller/requires.rb +83 -0
  45. data/lib/geminstaller/rogue_gem_finder.rb +195 -0
  46. data/lib/geminstaller/ruby_gem.rb +58 -0
  47. data/lib/geminstaller/rubygems_exit.rb +5 -0
  48. data/lib/geminstaller/rubygems_extensions.rb +9 -0
  49. data/lib/geminstaller/rubygems_version_checker.rb +21 -0
  50. data/lib/geminstaller/rubygems_version_warnings.rb +38 -0
  51. data/lib/geminstaller/source_index_search_adapter.rb +41 -0
  52. data/lib/geminstaller/unauthorized_dependency_prompt_error.rb +4 -0
  53. data/lib/geminstaller/unexpected_prompt_error.rb +4 -0
  54. data/lib/geminstaller/valid_platform_selector.rb +49 -0
  55. data/lib/geminstaller/version_specifier.rb +24 -0
  56. data/lib/geminstaller/yaml_loader.rb +22 -0
  57. data/lib/geminstaller.rb +103 -0
  58. data/lib/geminstaller_rails_preinitializer.rb +54 -0
  59. data/start_local_gem_server.sh +1 -0
  60. data/website/config.yaml +11 -0
  61. data/website/src/analytics.page +6 -0
  62. data/website/src/code/ci.virtual +5 -0
  63. data/website/src/code/coverage/index.virtual +5 -0
  64. data/website/src/code/index.page +92 -0
  65. data/website/src/code/rdoc/index.virtual +6 -0
  66. data/website/src/community/index.page +14 -0
  67. data/website/src/community/links.page +11 -0
  68. data/website/src/community/rubyforge.virtual +4 -0
  69. data/website/src/default.css +175 -0
  70. data/website/src/default.template +42 -0
  71. data/website/src/documentation/documentation.page +477 -0
  72. data/website/src/documentation/index.page +53 -0
  73. data/website/src/documentation/tutorials.page +337 -0
  74. data/website/src/download.page +12 -0
  75. data/website/src/faq.page +36 -0
  76. data/website/src/index.page +103 -0
  77. data/website/src/metainfo +54 -0
  78. data/website/src/webgen.css +112 -0
  79. metadata +139 -0
@@ -0,0 +1,31 @@
1
+ module GemInstaller
2
+ class FileReader
3
+ def read(file_path)
4
+ file_contents = nil
5
+ if !File.exist?(file_path) then
6
+ raise GemInstaller::MissingFileError.new("#{file_path}")
7
+ end
8
+
9
+ file = nil
10
+ begin
11
+ file = do_open(file_path)
12
+ rescue
13
+ raise GemInstaller::GemInstallerError.new("Error: Unable open file #{file_path}. Please ensure that this file can be opened.\n")
14
+ end
15
+
16
+ begin
17
+ do_read(file)
18
+ rescue
19
+ raise GemInstaller::GemInstallerError.new("Error: Unable read file #{file_path}. Please ensure that this file can be read.\n")
20
+ end
21
+ end
22
+
23
+ def do_open(file_path)
24
+ File.open(file_path)
25
+ end
26
+
27
+ def do_read(file)
28
+ file.read
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ module GemInstaller
2
+ class GemArgProcessor
3
+ #Common Options:
4
+ # --source URL Use URL as the remote source for gems
5
+ # -p, --[no-]http-proxy [URL] Use HTTP proxy for remote operations
6
+ # -h, --help Get help on this command
7
+ # -v, --verbose Set the verbose level of output
8
+ # --config-file FILE Use this config file instead of default
9
+ # --backtrace Show stack backtrace on errors
10
+ # --debug Turn on Ruby debugging
11
+ GEM_COMMON_OPTIONS_WITHOUT_ARG = ['--no-http-proxy','-v','--verbose','--backtrace','--debug']
12
+ GEM_COMMON_OPTIONS_WITH_ARG = ['--source','-p','--http_proxy','--config-file']
13
+
14
+ # take an array of args, and strip all args that are not common gem command args
15
+ def strip_non_common_gem_args(args)
16
+ # I can't figure out a way to elegantly do this, so I hardcoded the options. Here's how you print them
17
+ # Gem::Command.common_options.each do |option|
18
+ # p option[0]
19
+ # end
20
+
21
+ common_args = []
22
+ i = 0
23
+ loop do
24
+ break if i == args.size
25
+ arg = args[i]
26
+ if GEM_COMMON_OPTIONS_WITHOUT_ARG.include?(arg)
27
+ common_args << arg
28
+ else
29
+ GEM_COMMON_OPTIONS_WITH_ARG.each do |option|
30
+ if arg.include?(option)
31
+ common_args << arg
32
+ unless arg.include?('=')
33
+ i += 1
34
+ common_args << args[i]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ i += 1
40
+ end
41
+ common_args
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ module GemInstaller
2
+ class GemCommandManager
3
+ attr_writer :gem_spec_manager, :gem_runner_proxy
4
+ attr_writer :gem_interaction_handler if GemInstaller::RubyGemsVersionChecker.matches?('<=0.9.4')
5
+
6
+ def list_remote_gem(gem, additional_options)
7
+ run_args = ["list",gem.name,"--remote","--details"]
8
+ run_args << "--all" if GemInstaller::RubyGemsVersionChecker.matches?('>1.0.1')
9
+ run_args += additional_options
10
+ @gem_runner_proxy.run(run_args)
11
+ end
12
+
13
+ def uninstall_gem(gem)
14
+ return if !@gem_spec_manager.is_gem_installed?(gem)
15
+ init_gem_interaction_handler(gem)
16
+ run_gem_command('uninstall', gem, gem.uninstall_options)
17
+ end
18
+
19
+ def install_gem(gem, force_reinstall = false)
20
+ return [] if @gem_spec_manager.is_gem_installed?(gem) && !force_reinstall
21
+ init_gem_interaction_handler(gem)
22
+ run_gem_command('install', gem, gem.install_options)
23
+ end
24
+
25
+ def init_gem_interaction_handler(gem)
26
+ @gem_interaction_handler.dependent_gem = gem if GemInstaller::RubyGemsVersionChecker.matches?('<=0.9.4')
27
+ end
28
+
29
+ def dependency(name, version, additional_options = [])
30
+ # rubygems has bug up to 0.9.4 where the pipe options uses 'puts', instead of 'say', so we can't capture it
31
+ # with enhanced_stream_ui. Patched: http://rubyforge.org/tracker/index.php?func=detail&aid=9020&group_id=126&atid=577
32
+ # TODO: use pipe option on later versions which support it
33
+
34
+ name_regexp = "^#{name}$"
35
+ name_regexp = "/#{name_regexp}/" if GemInstaller::RubyGemsVersionChecker.matches?('>=1.2.0')
36
+ run_args = ["dependency", name_regexp, "--version", version]
37
+ run_args += additional_options
38
+ output_lines = @gem_runner_proxy.run(run_args)
39
+ # drop the first line which just echoes the dependent gem
40
+ output_lines.shift
41
+ # drop the line containing 'requires' (rubygems < 0.9.0)
42
+ if output_lines[0] == ' Requires'
43
+ output_lines.shift
44
+ end
45
+ # drop all empty lines
46
+ output_lines.reject! { |line| line == "" }
47
+ # strip leading space
48
+ output_lines.each { |line| line.strip! }
49
+ # convert into gems
50
+ output_gems = output_lines.collect do |line|
51
+ name = line.split(' ')[0]
52
+ version_spec = line.split(/[(,)]/)[1]
53
+ GemInstaller::RubyGem.new(name, :version => version_spec)
54
+ end
55
+ end
56
+
57
+ def run_gem_command(gem_command, gem, options)
58
+ run_args = [gem_command,gem.name,"--version", "#{gem.version}"]
59
+ if GemInstaller::RubyGemsVersionChecker.matches?('>=0.9.5')
60
+ run_args += ['--platform', "#{gem.platform}"] if gem.platform && !gem.platform.empty?
61
+ end
62
+ run_args += options
63
+ @gem_runner_proxy.run(run_args)
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+
70
+
71
+
@@ -0,0 +1,41 @@
1
+ module GemInstaller
2
+ class GemInteractionHandler
3
+ attr_writer :dependent_gem, :noninteractive_chooser_class, :valid_platform_selector
4
+ DEPENDENCY_PROMPT = 'Install required dependency'
5
+
6
+ def initialize
7
+ check_rubygems_version
8
+ end
9
+
10
+ def handle_ask_yes_no(question)
11
+ return unless question.index(DEPENDENCY_PROMPT)
12
+ message = "Error: RubyGems is prompting to install a required dependency, and you have not " +
13
+ "specified the '--install-dependencies' option for the current gem. You must modify your " +
14
+ "geminstaller config file to either specify the '--install-depencencies' (-y) " +
15
+ "option, or explicitly add an entry for the dependency gem earlier in the file.\n"
16
+ raise GemInstaller::UnauthorizedDependencyPromptError.new(message)
17
+ end
18
+
19
+ def handle_choose_from_list(question, list, noninteractive_chooser = nil)
20
+ noninteractive_chooser ||= @noninteractive_chooser_class.new
21
+ valid_platforms = nil
22
+ if dependent_gem_with_platform_specified?(list, noninteractive_chooser) or noninteractive_chooser.uninstall_list_type?(question)
23
+ valid_platforms = [@dependent_gem.platform]
24
+ else
25
+ valid_platforms = @valid_platform_selector.select(@dependent_gem.platform)
26
+ end
27
+ noninteractive_chooser.choose(question, list, @dependent_gem.name, @dependent_gem.version, valid_platforms)
28
+ end
29
+
30
+ def dependent_gem_with_platform_specified?(list, noninteractive_chooser)
31
+ noninteractive_chooser.dependent_gem?(@dependent_gem.name, list) and @dependent_gem.platform
32
+ end
33
+
34
+ def check_rubygems_version
35
+ if GemInstaller::RubyGemsVersionChecker.matches?('>=0.9.5')
36
+ # gem_interaction_handler is not used for RubyGems >= 0.9.5
37
+ raise RuntimeError.new("Internal GemInstaller Error: GemInteractionHandler should not be used for RubyGems >= 0.9.5")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ module GemInstaller
2
+ class GemListChecker
3
+ attr_writer :gem_command_manager, :gem_arg_processor, :version_specifier
4
+
5
+ def find_remote_matching_gem(gem)
6
+ gem_list_match_regexp = /^#{gem.regexp_escaped_name} \(.*/
7
+ common_args = @gem_arg_processor.strip_non_common_gem_args(gem.install_options)
8
+ remote_list = @gem_command_manager.list_remote_gem(gem, common_args)
9
+
10
+ matched_lines = []
11
+ remote_list.each do |line|
12
+ if line.match(gem_list_match_regexp)
13
+ matched_lines << line
14
+ end
15
+ end
16
+
17
+ if matched_lines.size > 1
18
+ error_message = "Error: more than one remote gem matches (this should not happen and is probably a bug in geminstaller). Gem name = '#{gem.name}', install options = '#{gem.install_options.join(' ')}'. Matching remote gems: \n"
19
+ matched_lines.each do |line|
20
+ error_message += line + "\n"
21
+ end
22
+ raise GemInstaller::GemInstallerError.new(error_message)
23
+ end
24
+
25
+ if matched_lines.size == 0
26
+ error_message = "Error: Could not find remote gem to install. Gem name = '#{gem.name}', Gem version = '#{gem.version}', install options = '#{gem.install_options.join(' ')}'. Your remote gem server may be having problems. You should try to list the remote gem yourself: 'gem list -r -v '#{gem.version}' #{common_args.join(' ')} #{gem.name}'. If it exists on the server, try running geminstaller again and report your experience here: http://thewoolleyweb.lighthouseapp.com/projects/11580-geminstaller/tickets/5-sometimes-installing-fails. Output of remote gem list command: \n#{remote_list}"
27
+ raise GemInstaller::GemInstallerError.new(error_message)
28
+ end
29
+
30
+ return matched_lines[0]
31
+ end
32
+
33
+ def verify_and_specify_remote_gem!(gem)
34
+ # TODO: this seems like it is a hack, but we must have a non-ambiguous version on the gem in order for
35
+ # noninteractive_chooser to be able to parse the gem list for multi-platform gems. This will not be necessary
36
+ # if a future RubyGems release allows specification/searching of the platform, because then we won't need noninteractive_chooser
37
+
38
+ version_list = available_versions(gem)
39
+ specified_version = @version_specifier.specify(gem.version, version_list, gem.name)
40
+ gem.version = specified_version
41
+ end
42
+
43
+ def available_versions(gem)
44
+ remote_match_line = find_remote_matching_gem(gem)
45
+ version_list = parse_out_version_list(remote_match_line)
46
+ end
47
+
48
+ def parse_out_version_list(line)
49
+ # return everything between first set of parenthesis
50
+ version_list = line.split(/[()]/)[1]
51
+ version_list
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ module GemInstaller
2
+ class GemRunnerProxy
3
+ attr_writer :gem_runner_class, :gem_cmd_manager_class, :output_listener, :exact_match_list_command
4
+ attr_writer :options, :enhanced_stream_ui, :output_filter
5
+
6
+ def run(args = [], stdin = [])
7
+ @output_filter.geminstaller_output(:commandecho,"'gem #{args.join(' ')}'\n")
8
+ gem_runner = create_gem_runner
9
+ # We have to manually initialize the configuration here, or else the GemCommandManager singleton
10
+ # will initialize with the (incorrect) default args when we call GemRunner.run.
11
+ gem_runner.do_configuration(args)
12
+ gem_cmd_manager = @gem_cmd_manager_class.instance
13
+ gem_cmd_manager.ui = @enhanced_stream_ui
14
+ gem_cmd_manager.commands[:list] = @exact_match_list_command
15
+
16
+ exit_status = nil
17
+ begin
18
+ # The call below is to work around RubyGem's behavior of caching (only) the last-used source when
19
+ # multiple gem commands for gems on different sources are issued via the API
20
+ Gem.sources.replace Gem.default_sources
21
+ gem_runner.run(args)
22
+ rescue SystemExit => system_exit
23
+ if GemInstaller::RubyGemsVersionChecker.matches?('>1.0.1')
24
+ raise system_exit unless system_exit.is_a? Gem::SystemExitException
25
+ exit_status = system_exit.message
26
+ else
27
+ raise system_exit
28
+ end
29
+ rescue GemInstaller::RubyGemsExit => normal_exit
30
+ exit_status = normal_exit.message
31
+ rescue GemInstaller::GemInstallerError => exit_error
32
+ raise_error_with_output(exit_error, args, @output_listener)
33
+ end
34
+ output_lines = @output_listener.read!
35
+ output_lines.push(exit_status) if exit_status
36
+ output_lines.collect do |line|
37
+ line.split("\n")
38
+ end.flatten
39
+ end
40
+
41
+ def create_gem_runner
42
+ if GemInstaller::RubyGemsVersionChecker.matches?('< 0.9')
43
+ @gem_runner_class.new()
44
+ else
45
+ @gem_runner_class.new(:command_manager => @gem_cmd_manager_class)
46
+ end
47
+ end
48
+
49
+ def raise_error_with_output(exit_error, args, listener)
50
+ error_class = exit_error.class
51
+ error_message = exit_error.message
52
+ gem_command_output = listener.read!
53
+ if gem_command_output.join('') =~ /Errno::EACCES/
54
+ error_message = access_error_message + "\n" + error_message
55
+ error_class = GemInstaller::GemInstallerAccessError
56
+ end
57
+ descriptive_exit_message = exit_error.descriptive_exit_message(error_message, 'gem', args, gem_command_output)
58
+ raise error_class.new(descriptive_exit_message)
59
+ end
60
+
61
+ def access_error_message
62
+ "You don't have permission to install a gem.\nThis is not a problem with GemInstaller.\nYou probably want use the --sudo option or run GemInstaller as sudo,\nor install your gems to a non-root-owned location.\nSee http://geminstaller.rubyforge.org/documentation/documentation.html#dealing_with_sudo\n"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ module GemInstaller
2
+ class GemSourceIndexProxy
3
+ attr_writer :gem_source_index
4
+
5
+ def refresh!
6
+ @gem_source_index.refresh!
7
+ end
8
+
9
+ # NOTE: We will require an exact version requirement, rather than the standard default Gem version_requirement of ">= 0"
10
+ # GemInstaller has it's own default defined elsewhere
11
+ def search(gem_pattern, version_requirement)
12
+ # Returns an array of Gem::Specification objects
13
+ @gem_source_index.search(gem_pattern, version_requirement)
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+
20
+
21
+
@@ -0,0 +1,53 @@
1
+ module GemInstaller
2
+ class GemSpecManager
3
+ attr_writer :source_index_search_adapter, :valid_platform_selector, :output_filter
4
+
5
+ def is_gem_installed?(gem)
6
+ return local_matching_gems(gem).size > 0
7
+ end
8
+
9
+ def all_local_gems
10
+ all_local_specs = @source_index_search_adapter.all_local_specs
11
+ return [] unless all_local_specs
12
+ all_local_gems = all_local_specs.collect do |spec|
13
+ gem = GemInstaller::RubyGem.new(spec.name, :version => spec.version.version)
14
+ end
15
+ return all_local_gems
16
+ end
17
+
18
+ def local_matching_gems(gem, exact_platform_match = true)
19
+ found_gem_specs = @source_index_search_adapter.search(gem,gem.version)
20
+ return [] unless found_gem_specs
21
+
22
+ if GemInstaller::RubyGemsVersionChecker.matches?('<=0.9.4')
23
+ found_gem_specs = found_gem_specs.select do |gem_spec|
24
+ valid_platforms = @valid_platform_selector.select(gem.platform, exact_platform_match)
25
+ valid_platforms.include?(gem_spec.platform)
26
+ end
27
+ else
28
+ # TODO: this is a hack because source_index#search doesn't allow specification of platform
29
+ if exact_platform_match
30
+ found_gem_specs = found_gem_specs.select do |spec|
31
+ spec_platform_matches?(spec, gem.platform)
32
+ end
33
+ end
34
+ end
35
+ matching_gems = found_gem_specs.map do |gem_spec|
36
+ GemInstaller::RubyGem.new(gem_spec.name, {:version => gem_spec.version.version, :platform => gem_spec.platform })
37
+ end
38
+ return matching_gems
39
+ end
40
+
41
+ def spec_platform_matches?(spec, platform)
42
+ if GemInstaller::RubyGemsVersionChecker.matches?('>0.9.5')
43
+ return Gem::Platform.new(spec.platform) == Gem::Platform.new(platform)
44
+ end
45
+ return spec.platform == platform
46
+ end
47
+ end
48
+ end
49
+
50
+
51
+
52
+
53
+
@@ -0,0 +1,4 @@
1
+ module GemInstaller
2
+ class GemInstallerAccessError < GemInstaller::GemInstallerError
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module GemInstaller
2
+ class GemInstallerError < RuntimeError
3
+ def descriptive_exit_message(message, command, args, gem_command_output)
4
+ args_string = args.join(" ")
5
+ descriptive_exit_message = "\n=========================================================\n"
6
+ descriptive_exit_message += "#{message}\n"
7
+ descriptive_exit_message += "Gem command was:\n #{command} #{args_string}\n\n"
8
+ descriptive_exit_message += "Gem command output was:\n"
9
+ descriptive_exit_message += gem_command_output.join("\n")
10
+ descriptive_exit_message += "\n=========================================================\n\n"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class IndependentHoe < Hoe
2
+ def extra_deps
3
+ []
4
+ end
5
+
6
+ def extra_dev_deps
7
+ []
8
+ end
9
+ end
@@ -0,0 +1,71 @@
1
+ module GemInstaller
2
+ class InstallProcessor
3
+ attr_writer :gem_list_checker, :gem_command_manager, :gem_spec_manager, :missing_dependency_finder, :options, :output_filter
4
+ def process(gems)
5
+ gems.each do |gem|
6
+ install_gem(gem)
7
+ end
8
+ end
9
+
10
+ def install_gem(gem)
11
+ already_specified = false
12
+ if gem.check_for_upgrade
13
+ @gem_list_checker.verify_and_specify_remote_gem!(gem)
14
+ already_specified = true
15
+ end
16
+ gem_is_installed = @gem_spec_manager.is_gem_installed?(gem)
17
+ if gem_is_installed
18
+ @output_filter.geminstaller_output(:debug,"Gem #{gem.name}, version #{gem.version} is already installed.\n")
19
+ else
20
+ perform_install(gem, already_specified)
21
+ installation_performed = true
22
+ end
23
+ if gem.fix_dependencies
24
+ if GemInstaller::RubyGemsVersionChecker.matches?('>=0.9.5')
25
+ # RubyGems >=0.9.5 automatically handles missing dependencies, so just perform an install
26
+ unless installation_performed
27
+ @output_filter.geminstaller_output(:install,"The 'fix_dependencies' option was specified for #{gem.name}, version #{gem.version}, so it will be reinstalled to fix any missing dependencies.\n")
28
+ perform_install(gem, already_specified, true)
29
+ end
30
+ else
31
+ # RubyGems <=0.9.4 does not automatically handles missing dependencies, so GemInstaller must find them
32
+ # manually with missing_dependency_finder
33
+ fix_dependencies(gem)
34
+ end
35
+ end
36
+ end
37
+
38
+ def perform_install(gem, already_specified, force_install = false)
39
+ @gem_list_checker.verify_and_specify_remote_gem!(gem) unless already_specified
40
+ @output_filter.geminstaller_output(:install,"Invoking gem install for #{gem.name}, version #{gem.version}.\n")
41
+ output_lines = @gem_command_manager.install_gem(gem, force_install)
42
+ print_dependency_install_messages(gem, output_lines) unless @options[:silent]
43
+ end
44
+
45
+ def print_dependency_install_messages(gem, output_lines)
46
+ output_lines.each do |line|
47
+ line =~ /Successfully installed /
48
+ match = $'
49
+ next unless match
50
+ next if match =~ /#{gem.name}-/
51
+ @output_filter.geminstaller_output(:install,"Rubygems automatically installed dependency gem #{match}\n")
52
+ end
53
+ end
54
+
55
+ def fix_dependencies(gem)
56
+ missing_dependencies = @missing_dependency_finder.find(gem)
57
+ while (missing_dependencies.size > 0)
58
+ missing_dependencies.each do |missing_dependency|
59
+ @output_filter.geminstaller_output(:install,"Installing #{missing_dependency.name} (#{missing_dependency.version})\n")
60
+ # recursively call install_gem to install the missing dependency. Since fix_dependencies
61
+ # should never be set on an auto-created missing dependency gem, there is no risk of an
62
+ # endless loop or stack overflow.
63
+ install_gem(missing_dependency)
64
+ end
65
+ # continue to look for and install missing dependencies until none are found
66
+ missing_dependencies = @missing_dependency_finder.find(gem)
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,46 @@
1
+ module GemInstaller
2
+ class MissingDependencyFinder
3
+ attr_writer :gem_command_manager, :gem_spec_manager, :gem_arg_processor, :output_filter
4
+ def find(dependent_gem)
5
+ if GemInstaller::RubyGemsVersionChecker.matches?('>=0.9.5')
6
+ # missing_dependency_finder is not used for RubyGems >= 0.9.5
7
+ raise RuntimeError.new("Internal GemInstaller Error: MissingDependencyFinder should not be used for RubyGems >= 0.9.5")
8
+ end
9
+
10
+ # NOTE: this doesn't resolve platforms, there's currently no way to know what
11
+ # platform should be selected for a dependency gem. Best-effort handling
12
+ # of ambiguous platforms on dependency gems will be handled elsewhere
13
+ matching_dependent_gems = @gem_spec_manager.local_matching_gems(dependent_gem)
14
+ missing_dependencies = []
15
+ install_options = dependent_gem.install_options
16
+ add_include_dependency_option(install_options)
17
+ common_args = @gem_arg_processor.strip_non_common_gem_args(install_options)
18
+ matching_dependent_gems.each do |matching_dependent_gem|
19
+ message_already_printed = false
20
+ dependency_gems = @gem_command_manager.dependency(matching_dependent_gem.name, matching_dependent_gem.version.to_s, common_args)
21
+ dependency_gems.each do |dependency_gem|
22
+ dependency_gem.install_options = install_options
23
+ local_matching_dependency_gems = @gem_spec_manager.local_matching_gems(dependency_gem, false)
24
+ unless local_matching_dependency_gems.size > 0
25
+ unless message_already_printed
26
+ @output_filter.geminstaller_output(:info, "Missing dependencies found for #{matching_dependent_gem.name} (#{matching_dependent_gem.version}):\n")
27
+ message_already_printed = true
28
+ end
29
+ # TODO: print install options too?
30
+ @output_filter.geminstaller_output(:info, " #{dependency_gem.name} (#{dependency_gem.version})\n")
31
+ missing_dependencies << dependency_gem
32
+ end
33
+ # recurse to find any missing dependencies in the tree
34
+ sub_dependencies = find(dependency_gem)
35
+ missing_dependencies += sub_dependencies if sub_dependencies.size > 0
36
+ end
37
+ end
38
+ return missing_dependencies
39
+ end
40
+
41
+ def add_include_dependency_option(install_options)
42
+ return if install_options.index('-y') or install_options.index('--include-dependencies')
43
+ install_options << '--include-dependencies'
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ module GemInstaller
2
+ class MissingFileError < GemInstaller::GemInstallerError
3
+ end
4
+ end
@@ -0,0 +1,114 @@
1
+ module GemInstaller
2
+ # Format for "install" prompt list: "#{spec.name} #{spec.version} (#{spec.platform})"
3
+ # Format for "uninstall" prompt list: "#{spec.name}-#{spec.version}" for ruby, "#{spec.name}-#{spec.version}-#{spec.platform}" (gem.full_name) for binary
4
+ class NoninteractiveChooser
5
+ def initialize
6
+ check_rubygems_version
7
+ @question = nil
8
+ end
9
+
10
+ def choose(question, list, dependent_gem_name, dependent_gem_version, valid_platforms)
11
+ @question = question
12
+ @list = list
13
+ @dependent_gem_name = dependent_gem_name
14
+ @dependent_gem_version = dependent_gem_version
15
+ @valid_platforms = valid_platforms
16
+ raise GemInstaller::GemInstallerError.new("Internal GemInstaller Error, unexpected choice prompt question: '#{question}'") unless
17
+ install_list_type? or uninstall_list_type?
18
+
19
+ raise GemInstaller::GemInstallerError.new("valid_platforms must be passed as an array.") unless
20
+ valid_platforms.respond_to? :shift
21
+
22
+ raise GemInstaller::GemInstallerError.new("Internal GemInstaller Error, multiple platforms cannot be specified for an uninstall prompt") if
23
+ uninstall_list_type? and valid_platforms.size > 1
24
+
25
+ index = nil
26
+ valid_platforms.each do |platform|
27
+ index = find_matching_line(platform)
28
+ break if index
29
+ end
30
+
31
+ return @list[index], index if index
32
+
33
+ # if we didn't find a match, raise a descriptive error
34
+ action = install_list_type? ? 'install' : 'uninstall'
35
+ name = @dependent_gem_name ? "#{@dependent_gem_name}" : "'any name'"
36
+ version = @dependent_gem_version ? "#{@dependent_gem_version}" : "'any version'"
37
+ platform = @valid_platforms ? " (#{@valid_platforms.join(' or ')})" : ''
38
+ error_message = "Error: Unable to select gem from list: \"#{name} #{version}#{platform}\". Available gems are:\n"
39
+ list.each do |item|
40
+ # skip last 'cancel' list entry, it's not a real gem
41
+ next if item == list.last
42
+ error_message += " " + item + "\n"
43
+ end
44
+ raise GemInstaller::GemInstallerError.new(error_message)
45
+ end
46
+
47
+ def find_matching_line(platform)
48
+ required_list_item_regexp = required_name_and_version_regexp + required_platform_regexp(platform)
49
+ line_selector = /^#{required_list_item_regexp}$/
50
+ @list.each_with_index do |item, index|
51
+ if item =~ line_selector
52
+ return index
53
+ end
54
+ end
55
+ return nil
56
+ end
57
+
58
+ def required_name_and_version_regexp
59
+ required_name_regexp = ".*?"
60
+ required_version_regexp = ".*?"
61
+ if uninstall_list_type? or dependent_gem?
62
+ # match the exact name and version if it's an uninstall prompt or a dependent gem
63
+ required_name_regexp = Regexp.escape(@dependent_gem_name) if @dependent_gem_name
64
+ required_version_regexp = Regexp.escape(@dependent_gem_version) if @dependent_gem_version
65
+ end
66
+ "#{required_name_regexp}[\s-]{0,1}#{required_version_regexp}"
67
+ end
68
+
69
+ def required_platform_regexp(platform_to_match)
70
+ return "" unless platform_to_match
71
+ required_platform = ""
72
+ escaped_platform_to_match = Regexp.escape(platform_to_match)
73
+ platform_regex = nil
74
+ if dependent_gem?
75
+ # do an exact match on the platform if this is the dependent gem
76
+ platform_regex = "#{escaped_platform_to_match}"
77
+ else
78
+ # do a wildcard match on the platform if it's not the dependent gem
79
+ platform_regex = ".*?#{escaped_platform_to_match}.*?"
80
+ end
81
+ # install list types always have the platform for each gem in parenthesis, even if it is ruby
82
+ if (install_list_type?)
83
+ required_platform = " \\(#{platform_regex}\\)"
84
+ end
85
+ # uninstall list types have the gem full_name, which is the platform for each gem appended after a dash, but only if it is not ruby
86
+ if (uninstall_list_type? && platform_to_match.to_s != GemInstaller::RubyGem.default_platform)
87
+ required_platform = "-.*?#{platform_regex}.*?"
88
+ end
89
+ required_platform
90
+ end
91
+
92
+ def install_list_type?(question = @question)
93
+ question =~ /to install/
94
+ end
95
+
96
+ def uninstall_list_type?(question = @question)
97
+ question =~ /to uninstall/
98
+ end
99
+
100
+ def dependent_gem?(dependent_gem_name = @dependent_gem_name, list = @list)
101
+ # return true if it's an install prompt, and the list contains the gem for which the original install request
102
+ # was made (in other words, it's a dependent gem, not a dependency gem)
103
+ install_format_exact_name_match_regexp = /^#{dependent_gem_name}\s.*/
104
+ install_list_type? and list[0] =~ install_format_exact_name_match_regexp
105
+ end
106
+
107
+ def check_rubygems_version
108
+ if GemInstaller::RubyGemsVersionChecker.matches?('>=0.9.5')
109
+ # noninteractive_chooser is not used for RubyGems >= 0.9.5
110
+ raise RuntimeError.new("Internal GemInstaller Error: NoninteractiveChooser should not be used for RubyGems >= 0.9.5")
111
+ end
112
+ end
113
+ end
114
+ end