carat 1.9.9.pre1

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.
Files changed (184) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +24 -0
  5. data/CHANGELOG.md +2006 -0
  6. data/CODE_OF_CONDUCT.md +40 -0
  7. data/CONTRIBUTING.md +23 -0
  8. data/DEVELOPMENT.md +119 -0
  9. data/ISSUES.md +96 -0
  10. data/LICENSE.md +23 -0
  11. data/README.md +32 -0
  12. data/Rakefile +308 -0
  13. data/bin/carat +21 -0
  14. data/bin/carat_ruby +56 -0
  15. data/carat.gemspec +32 -0
  16. data/lib/carat.rb +446 -0
  17. data/lib/carat/anonymizable_uri.rb +32 -0
  18. data/lib/carat/capistrano.rb +16 -0
  19. data/lib/carat/cli.rb +407 -0
  20. data/lib/carat/cli/binstubs.rb +38 -0
  21. data/lib/carat/cli/cache.rb +35 -0
  22. data/lib/carat/cli/check.rb +35 -0
  23. data/lib/carat/cli/clean.rb +26 -0
  24. data/lib/carat/cli/common.rb +56 -0
  25. data/lib/carat/cli/config.rb +84 -0
  26. data/lib/carat/cli/console.rb +38 -0
  27. data/lib/carat/cli/exec.rb +44 -0
  28. data/lib/carat/cli/gem.rb +195 -0
  29. data/lib/carat/cli/init.rb +33 -0
  30. data/lib/carat/cli/inject.rb +33 -0
  31. data/lib/carat/cli/install.rb +156 -0
  32. data/lib/carat/cli/open.rb +23 -0
  33. data/lib/carat/cli/outdated.rb +80 -0
  34. data/lib/carat/cli/package.rb +45 -0
  35. data/lib/carat/cli/platform.rb +43 -0
  36. data/lib/carat/cli/show.rb +74 -0
  37. data/lib/carat/cli/update.rb +73 -0
  38. data/lib/carat/cli/viz.rb +27 -0
  39. data/lib/carat/constants.rb +5 -0
  40. data/lib/carat/current_ruby.rb +183 -0
  41. data/lib/carat/definition.rb +628 -0
  42. data/lib/carat/dep_proxy.rb +43 -0
  43. data/lib/carat/dependency.rb +110 -0
  44. data/lib/carat/deployment.rb +59 -0
  45. data/lib/carat/deprecate.rb +15 -0
  46. data/lib/carat/dsl.rb +331 -0
  47. data/lib/carat/endpoint_specification.rb +76 -0
  48. data/lib/carat/env.rb +75 -0
  49. data/lib/carat/environment.rb +42 -0
  50. data/lib/carat/fetcher.rb +423 -0
  51. data/lib/carat/friendly_errors.rb +85 -0
  52. data/lib/carat/gem_helper.rb +180 -0
  53. data/lib/carat/gem_helpers.rb +26 -0
  54. data/lib/carat/gem_installer.rb +9 -0
  55. data/lib/carat/gem_path_manipulation.rb +8 -0
  56. data/lib/carat/gem_tasks.rb +2 -0
  57. data/lib/carat/graph.rb +169 -0
  58. data/lib/carat/index.rb +197 -0
  59. data/lib/carat/injector.rb +64 -0
  60. data/lib/carat/installer.rb +339 -0
  61. data/lib/carat/lazy_specification.rb +83 -0
  62. data/lib/carat/lockfile_parser.rb +167 -0
  63. data/lib/carat/match_platform.rb +13 -0
  64. data/lib/carat/psyched_yaml.rb +26 -0
  65. data/lib/carat/remote_specification.rb +57 -0
  66. data/lib/carat/resolver.rb +334 -0
  67. data/lib/carat/retry.rb +60 -0
  68. data/lib/carat/ruby_dsl.rb +11 -0
  69. data/lib/carat/ruby_version.rb +117 -0
  70. data/lib/carat/rubygems_ext.rb +170 -0
  71. data/lib/carat/rubygems_integration.rb +619 -0
  72. data/lib/carat/runtime.rb +289 -0
  73. data/lib/carat/settings.rb +208 -0
  74. data/lib/carat/setup.rb +24 -0
  75. data/lib/carat/shared_helpers.rb +149 -0
  76. data/lib/carat/similarity_detector.rb +63 -0
  77. data/lib/carat/source.rb +46 -0
  78. data/lib/carat/source/git.rb +294 -0
  79. data/lib/carat/source/git/git_proxy.rb +162 -0
  80. data/lib/carat/source/path.rb +226 -0
  81. data/lib/carat/source/path/installer.rb +43 -0
  82. data/lib/carat/source/rubygems.rb +381 -0
  83. data/lib/carat/source_list.rb +101 -0
  84. data/lib/carat/spec_set.rb +154 -0
  85. data/lib/carat/ssl_certs/.document +1 -0
  86. data/lib/carat/ssl_certs/AddTrustExternalCARoot-2048.pem +25 -0
  87. data/lib/carat/ssl_certs/AddTrustExternalCARoot.pem +32 -0
  88. data/lib/carat/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +14 -0
  89. data/lib/carat/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +23 -0
  90. data/lib/carat/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +28 -0
  91. data/lib/carat/ssl_certs/GeoTrustGlobalCA.pem +20 -0
  92. data/lib/carat/ssl_certs/certificate_manager.rb +66 -0
  93. data/lib/carat/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
  94. data/lib/carat/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +23 -0
  95. data/lib/carat/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +25 -0
  96. data/lib/carat/templates/Executable +16 -0
  97. data/lib/carat/templates/Executable.standalone +12 -0
  98. data/lib/carat/templates/Gemfile +4 -0
  99. data/lib/carat/templates/newgem/.travis.yml.tt +3 -0
  100. data/lib/carat/templates/newgem/CODE_OF_CONDUCT.md.tt +13 -0
  101. data/lib/carat/templates/newgem/Gemfile.tt +4 -0
  102. data/lib/carat/templates/newgem/LICENSE.txt.tt +21 -0
  103. data/lib/carat/templates/newgem/README.md.tt +39 -0
  104. data/lib/carat/templates/newgem/Rakefile.tt +25 -0
  105. data/lib/carat/templates/newgem/bin/console.tt +14 -0
  106. data/lib/carat/templates/newgem/bin/setup.tt +7 -0
  107. data/lib/carat/templates/newgem/exe/newgem.tt +3 -0
  108. data/lib/carat/templates/newgem/ext/newgem/extconf.rb.tt +3 -0
  109. data/lib/carat/templates/newgem/ext/newgem/newgem.c.tt +9 -0
  110. data/lib/carat/templates/newgem/ext/newgem/newgem.h.tt +6 -0
  111. data/lib/carat/templates/newgem/gitignore.tt +16 -0
  112. data/lib/carat/templates/newgem/lib/newgem.rb.tt +12 -0
  113. data/lib/carat/templates/newgem/lib/newgem/version.rb.tt +7 -0
  114. data/lib/carat/templates/newgem/newgem.gemspec.tt +43 -0
  115. data/lib/carat/templates/newgem/rspec.tt +2 -0
  116. data/lib/carat/templates/newgem/spec/newgem_spec.rb.tt +11 -0
  117. data/lib/carat/templates/newgem/spec/spec_helper.rb.tt +2 -0
  118. data/lib/carat/templates/newgem/test/minitest_helper.rb.tt +4 -0
  119. data/lib/carat/templates/newgem/test/test_newgem.rb.tt +11 -0
  120. data/lib/carat/ui.rb +7 -0
  121. data/lib/carat/ui/rg_proxy.rb +21 -0
  122. data/lib/carat/ui/shell.rb +103 -0
  123. data/lib/carat/ui/silent.rb +44 -0
  124. data/lib/carat/vendor/molinillo/lib/molinillo.rb +5 -0
  125. data/lib/carat/vendor/molinillo/lib/molinillo/dependency_graph.rb +266 -0
  126. data/lib/carat/vendor/molinillo/lib/molinillo/errors.rb +69 -0
  127. data/lib/carat/vendor/molinillo/lib/molinillo/gem_metadata.rb +3 -0
  128. data/lib/carat/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +90 -0
  129. data/lib/carat/vendor/molinillo/lib/molinillo/modules/ui.rb +63 -0
  130. data/lib/carat/vendor/molinillo/lib/molinillo/resolution.rb +415 -0
  131. data/lib/carat/vendor/molinillo/lib/molinillo/resolver.rb +43 -0
  132. data/lib/carat/vendor/molinillo/lib/molinillo/state.rb +43 -0
  133. data/lib/carat/vendor/net/http/faster.rb +26 -0
  134. data/lib/carat/vendor/net/http/persistent.rb +1230 -0
  135. data/lib/carat/vendor/net/http/persistent/ssl_reuse.rb +128 -0
  136. data/lib/carat/vendor/thor/lib/thor.rb +484 -0
  137. data/lib/carat/vendor/thor/lib/thor/actions.rb +319 -0
  138. data/lib/carat/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  139. data/lib/carat/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  140. data/lib/carat/vendor/thor/lib/thor/actions/directory.rb +118 -0
  141. data/lib/carat/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  142. data/lib/carat/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
  143. data/lib/carat/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
  144. data/lib/carat/vendor/thor/lib/thor/base.rb +656 -0
  145. data/lib/carat/vendor/thor/lib/thor/command.rb +133 -0
  146. data/lib/carat/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
  147. data/lib/carat/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
  148. data/lib/carat/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
  149. data/lib/carat/vendor/thor/lib/thor/error.rb +32 -0
  150. data/lib/carat/vendor/thor/lib/thor/group.rb +281 -0
  151. data/lib/carat/vendor/thor/lib/thor/invocation.rb +178 -0
  152. data/lib/carat/vendor/thor/lib/thor/line_editor.rb +17 -0
  153. data/lib/carat/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  154. data/lib/carat/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  155. data/lib/carat/vendor/thor/lib/thor/parser.rb +4 -0
  156. data/lib/carat/vendor/thor/lib/thor/parser/argument.rb +73 -0
  157. data/lib/carat/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  158. data/lib/carat/vendor/thor/lib/thor/parser/option.rb +125 -0
  159. data/lib/carat/vendor/thor/lib/thor/parser/options.rb +218 -0
  160. data/lib/carat/vendor/thor/lib/thor/rake_compat.rb +71 -0
  161. data/lib/carat/vendor/thor/lib/thor/runner.rb +322 -0
  162. data/lib/carat/vendor/thor/lib/thor/shell.rb +81 -0
  163. data/lib/carat/vendor/thor/lib/thor/shell/basic.rb +421 -0
  164. data/lib/carat/vendor/thor/lib/thor/shell/color.rb +149 -0
  165. data/lib/carat/vendor/thor/lib/thor/shell/html.rb +126 -0
  166. data/lib/carat/vendor/thor/lib/thor/util.rb +267 -0
  167. data/lib/carat/vendor/thor/lib/thor/version.rb +3 -0
  168. data/lib/carat/vendored_fileutils.rb +9 -0
  169. data/lib/carat/vendored_molinillo.rb +2 -0
  170. data/lib/carat/vendored_persistent.rb +11 -0
  171. data/lib/carat/vendored_thor.rb +3 -0
  172. data/lib/carat/version.rb +6 -0
  173. data/lib/carat/vlad.rb +11 -0
  174. data/lib/carat/worker.rb +73 -0
  175. data/man/carat-config.ronn +178 -0
  176. data/man/carat-exec.ronn +136 -0
  177. data/man/carat-install.ronn +383 -0
  178. data/man/carat-package.ronn +66 -0
  179. data/man/carat-platform.ronn +42 -0
  180. data/man/carat-update.ronn +188 -0
  181. data/man/carat.ronn +98 -0
  182. data/man/gemfile.5.ronn +473 -0
  183. data/man/index.txt +7 -0
  184. metadata +321 -0
@@ -0,0 +1,197 @@
1
+ require "set"
2
+
3
+ module Carat
4
+ class Index
5
+ include Enumerable
6
+
7
+ def self.build
8
+ i = new
9
+ yield i
10
+ i
11
+ end
12
+
13
+ attr_reader :specs, :all_specs, :sources
14
+ protected :specs, :all_specs
15
+
16
+ def initialize
17
+ @sources = []
18
+ @cache = {}
19
+ @specs = Hash.new { |h,k| h[k] = Hash.new }
20
+ @all_specs = Hash.new { |h,k| h[k] = [] }
21
+ end
22
+
23
+ def initialize_copy(o)
24
+ super
25
+ @sources = @sources.dup
26
+ @cache = {}
27
+ @specs = Hash.new { |h,k| h[k] = Hash.new }
28
+ @all_specs = Hash.new { |h,k| h[k] = [] }
29
+
30
+ o.specs.each do |name, hash|
31
+ @specs[name] = hash.dup
32
+ end
33
+ o.all_specs.each do |name, array|
34
+ @all_specs[name] = array.dup
35
+ end
36
+ end
37
+
38
+ def inspect
39
+ "#<#{self.class}:0x#{object_id} sources=#{sources.map{|s| s.inspect}} specs.size=#{specs.size}>"
40
+ end
41
+
42
+ def empty?
43
+ each { return false }
44
+ true
45
+ end
46
+
47
+ def search_all(name)
48
+ all_matches = @all_specs[name] + local_search(name)
49
+ @sources.each do |source|
50
+ all_matches.concat(source.search_all(name))
51
+ end
52
+ all_matches
53
+ end
54
+
55
+ # Search this index's specs, and any source indexes that this index knows
56
+ # about, returning all of the results.
57
+ def search(query, base = nil)
58
+ results = local_search(query, base)
59
+ seen = Set.new(results.map { |spec| [spec.name, spec.version, spec.platform] })
60
+
61
+ @sources.each do |source|
62
+ source.search(query, base).each do |spec|
63
+ lookup = [spec.name, spec.version, spec.platform]
64
+ unless seen.include?(lookup)
65
+ results << spec
66
+ seen << lookup
67
+ end
68
+ end
69
+ end
70
+
71
+ results.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
72
+ end
73
+
74
+ def local_search(query, base = nil)
75
+ case query
76
+ when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
77
+ when String then specs_by_name(query)
78
+ when Gem::Dependency then search_by_dependency(query, base)
79
+ else
80
+ raise "You can't search for a #{query.inspect}."
81
+ end
82
+ end
83
+
84
+ alias [] search
85
+
86
+ def <<(spec)
87
+ @specs[spec.name]["#{spec.version}-#{spec.platform}"] = spec
88
+
89
+ spec
90
+ end
91
+
92
+ def each(&blk)
93
+ specs.values.each do |spec_sets|
94
+ spec_sets.values.each(&blk)
95
+ end
96
+ end
97
+
98
+ # returns a list of the dependencies
99
+ def unmet_dependency_names
100
+ names = dependency_names
101
+ names.delete_if{|n| n == "carat" }
102
+ names.select{|n| search(n).empty? }
103
+ end
104
+
105
+ def dependency_names
106
+ names = []
107
+ each{|s| names.push(*s.dependencies.map{|d| d.name }) }
108
+ names.uniq
109
+ end
110
+
111
+ def use(other, override_dupes = false)
112
+ return unless other
113
+ other.each do |s|
114
+ if (dupes = search_by_spec(s)) && dupes.any?
115
+ @all_specs[s.name] = [s] + dupes
116
+ next unless override_dupes
117
+ self << s
118
+ end
119
+ self << s
120
+ end
121
+ self
122
+ end
123
+
124
+ def size
125
+ @sources.inject(@specs.size) do |size, source|
126
+ size += source.size
127
+ end
128
+ end
129
+
130
+ def ==(o)
131
+ all? do |spec|
132
+ other_spec = o[spec].first
133
+ (spec.dependencies & other_spec.dependencies).empty? && spec.source == other_spec.source
134
+ end
135
+ end
136
+
137
+ def add_source(index)
138
+ if index.is_a?(Index)
139
+ @sources << index
140
+ @sources.uniq! # need to use uniq! here instead of checking for the item before adding
141
+ else
142
+ raise ArgumentError, "Source must be an index, not #{index.class}"
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ def specs_by_name(name)
149
+ @specs[name].values
150
+ end
151
+
152
+ def search_by_dependency(dependency, base = nil)
153
+ @cache[base || false] ||= {}
154
+ @cache[base || false][dependency] ||= begin
155
+ specs = specs_by_name(dependency.name) + (base || [])
156
+ found = specs.select do |spec|
157
+ if base # allow all platforms when searching from a lockfile
158
+ dependency.matches_spec?(spec)
159
+ else
160
+ dependency.matches_spec?(spec) && Gem::Platform.match(spec.platform)
161
+ end
162
+ end
163
+
164
+ wants_prerelease = dependency.requirement.prerelease?
165
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
166
+
167
+ unless wants_prerelease || only_prerelease
168
+ found.reject! { |spec| spec.version.prerelease? }
169
+ end
170
+
171
+ found
172
+ end
173
+ end
174
+
175
+ def search_by_spec(spec)
176
+ spec = @specs[spec.name]["#{spec.version}-#{spec.platform}"]
177
+ spec ? [spec] : []
178
+ end
179
+
180
+ if RUBY_VERSION < '1.9'
181
+ def same_version?(a, b)
182
+ regex = /^(.*?)(?:\.0)*$/
183
+ a.to_s[regex, 1] == b.to_s[regex, 1]
184
+ end
185
+ else
186
+ def same_version?(a, b)
187
+ a == b
188
+ end
189
+ end
190
+
191
+ def spec_satisfies_dependency?(spec, dep)
192
+ return false unless dep.name == spec.name
193
+ dep.requirement.satisfied_by?(spec.version)
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,64 @@
1
+ module Carat
2
+ class Injector
3
+ def self.inject(new_deps)
4
+ injector = new(new_deps)
5
+ injector.inject(Carat.default_gemfile, Carat.default_lockfile)
6
+ end
7
+
8
+ def initialize(new_deps)
9
+ @new_deps = new_deps
10
+ end
11
+
12
+ def inject(gemfile_path, lockfile_path)
13
+ if Carat.settings[:frozen]
14
+ # ensure the lock and Gemfile are synced
15
+ Carat.definition.ensure_equivalent_gemfile_and_lockfile(true)
16
+ # temporarily remove frozen while we inject
17
+ frozen = Carat.settings.delete(:frozen)
18
+ end
19
+
20
+ # evaluate the Gemfile we have now
21
+ builder = Dsl.new
22
+ builder.eval_gemfile(gemfile_path)
23
+
24
+ # don't inject any gems that are already in the Gemfile
25
+ @new_deps -= builder.dependencies
26
+
27
+ # add new deps to the end of the in-memory Gemfile
28
+ builder.eval_gemfile("injected gems", new_gem_lines) if @new_deps.any?
29
+
30
+ # resolve to see if the new deps broke anything
31
+ definition = builder.to_definition(lockfile_path, {})
32
+ definition.resolve_remotely!
33
+
34
+ # since nothing broke, we can add those gems to the gemfile
35
+ append_to(gemfile_path) if @new_deps.any?
36
+
37
+ # since we resolved successfully, write out the lockfile
38
+ definition.lock(Carat.default_lockfile)
39
+
40
+ # return an array of the deps that we added
41
+ return @new_deps
42
+ ensure
43
+ Carat.settings[:frozen] = '1' if frozen
44
+ end
45
+
46
+ private
47
+
48
+ def new_gem_lines
49
+ @new_deps.map do |d|
50
+ %|gem '#{d.name}', '#{d.requirement}'|
51
+ end.join("\n")
52
+ end
53
+
54
+ def append_to(gemfile_path)
55
+ gemfile_path.open("a") do |f|
56
+ f.puts
57
+ f.puts "# Added at #{Time.now} by #{`whoami`.chomp}:"
58
+ f.puts new_gem_lines
59
+ end
60
+ end
61
+
62
+
63
+ end
64
+ end
@@ -0,0 +1,339 @@
1
+ require 'erb'
2
+ require 'rubygems/dependency_installer'
3
+ require 'carat/worker'
4
+
5
+ module Carat
6
+ class Installer < Environment
7
+ class << self
8
+ attr_accessor :post_install_messages, :ambiguous_gems
9
+
10
+ Installer.post_install_messages = {}
11
+ Installer.ambiguous_gems = []
12
+ end
13
+
14
+ # Begins the installation process for Carat.
15
+ # For more information see the #run method on this class.
16
+ def self.install(root, definition, options = {})
17
+ installer = new(root, definition)
18
+ installer.run(options)
19
+ installer
20
+ end
21
+
22
+ # Runs the install procedures for a specific Gemfile.
23
+ #
24
+ # Firstly, this method will check to see if Carat.bundle_path exists
25
+ # and if not then will create it. This is usually the location of gems
26
+ # on the system, be it RVM or at a system path.
27
+ #
28
+ # Secondly, it checks if Carat has been configured to be "frozen"
29
+ # Frozen ensures that the Gemfile and the Gemfile.lock file are matching.
30
+ # This stops a situation where a developer may update the Gemfile but may not run
31
+ # `carat install`, which leads to the Gemfile.lock file not being correctly updated.
32
+ # If this file is not correctly updated then any other developer running
33
+ # `carat install` will potentially not install the correct gems.
34
+ #
35
+ # Thirdly, Carat checks if there are any dependencies specified in the Gemfile using
36
+ # Carat::Environment#dependencies. If there are no dependencies specified then
37
+ # Carat returns a warning message stating so and this method returns.
38
+ #
39
+ # Fourthly, Carat checks if the default lockfile (Gemfile.lock) exists, and if so
40
+ # then proceeds to set up a defintion based on the default gemfile (Gemfile) and the
41
+ # default lock file (Gemfile.lock). However, this is not the case if the platform is different
42
+ # to that which is specified in Gemfile.lock, or if there are any missing specs for the gems.
43
+ #
44
+ # Fifthly, Carat resolves the dependencies either through a cache of gems or by remote.
45
+ # This then leads into the gems being installed, along with stubs for their executables,
46
+ # but only if the --binstubs option has been passed or Carat.options[:bin] has been set
47
+ # earlier.
48
+ #
49
+ # Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time
50
+ # that a user runs `carat install` they will receive any updates from this process.
51
+ #
52
+ # Finally: TODO add documentation for how the standalone process works.
53
+ def run(options)
54
+ create_bundle_path
55
+
56
+ if Carat.settings[:frozen]
57
+ @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
58
+ end
59
+
60
+ if dependencies.empty?
61
+ Carat.ui.warn "The Gemfile specifies no dependencies"
62
+ lock
63
+ return
64
+ end
65
+
66
+ if Carat.default_lockfile.exist? && !options["update"]
67
+ local = Carat.ui.silence do
68
+ begin
69
+ tmpdef = Definition.build(Carat.default_gemfile, Carat.default_lockfile, nil)
70
+ true unless tmpdef.new_platform? || tmpdef.missing_specs.any?
71
+ rescue CaratError
72
+ end
73
+ end
74
+ end
75
+
76
+ # Since we are installing, we can resolve the definition
77
+ # using remote specs
78
+ unless local
79
+ options["local"] ? @definition.resolve_with_cache! : @definition.resolve_remotely!
80
+ end
81
+
82
+ # the order that the resolver provides is significant, since
83
+ # dependencies might actually affect the installation of a gem.
84
+ # that said, it's a rare situation (other than rake), and parallel
85
+ # installation is just SO MUCH FASTER. so we let people opt in.
86
+ jobs = [Carat.settings[:jobs].to_i-1, 1].max
87
+ if jobs > 1 && can_install_in_parallel?
88
+ install_in_parallel jobs, options[:standalone]
89
+ else
90
+ install_sequentially options[:standalone]
91
+ end
92
+
93
+ lock unless Carat.settings[:frozen]
94
+ generate_standalone(options[:standalone]) if options[:standalone]
95
+ end
96
+
97
+ def install_gem_from_spec(spec, standalone = false, worker = 0)
98
+ # Fetch the build settings, if there are any
99
+ settings = Carat.settings["build.#{spec.name}"]
100
+ messages = nil
101
+
102
+ if settings
103
+ Carat.rubygems.with_build_args [settings] do
104
+ messages = spec.source.install(spec)
105
+ end
106
+ else
107
+ messages = spec.source.install(spec)
108
+ end
109
+
110
+ install_message, post_install_message, debug_message = *messages
111
+
112
+ if install_message.include? 'Installing'
113
+ Carat.ui.confirm install_message
114
+ else
115
+ Carat.ui.info install_message
116
+ end
117
+ Carat.ui.debug debug_message if debug_message
118
+ Carat.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
119
+
120
+ if Carat.settings[:bin] && standalone
121
+ generate_standalone_carat_executable_stubs(spec)
122
+ elsif Carat.settings[:bin]
123
+ generate_carat_executable_stubs(spec, :force => true)
124
+ end
125
+
126
+ post_install_message
127
+ rescue Errno::ENOSPC
128
+ raise Carat::InstallError, "Your disk is out of space. Free some " \
129
+ "space to be able to install your bundle."
130
+ rescue Exception => e
131
+ # if install hook failed or gem signature is bad, just die
132
+ raise e if e.is_a?(Carat::InstallHookError) || e.is_a?(Carat::SecurityError)
133
+
134
+ # other failure, likely a native extension build failure
135
+ Carat.ui.info ""
136
+ Carat.ui.warn "#{e.class}: #{e.message}"
137
+ msg = "An error occurred while installing #{spec.name} (#{spec.version}),"
138
+ msg << " and Carat cannot continue."
139
+
140
+ unless spec.source.options["git"]
141
+ msg << "\nMake sure that `gem install"
142
+ msg << " #{spec.name} -v '#{spec.version}'` succeeds before bundling."
143
+ end
144
+ Carat.ui.debug e.backtrace.join("\n")
145
+ raise Carat::InstallError, msg
146
+ end
147
+
148
+ def generate_carat_executable_stubs(spec, options = {})
149
+ if options[:binstubs_cmd] && spec.executables.empty?
150
+ options = {}
151
+ spec.runtime_dependencies.each do |dep|
152
+ bins = @definition.specs[dep].first.executables
153
+ options[dep.name] = bins unless bins.empty?
154
+ end
155
+ if options.any?
156
+ Carat.ui.warn "#{spec.name} has no executables, but you may want " +
157
+ "one from a gem it depends on."
158
+ options.each{|name,bins| Carat.ui.warn " #{name} has: #{bins.join(', ')}" }
159
+ else
160
+ Carat.ui.warn "There are no executables for the gem #{spec.name}."
161
+ end
162
+ return
163
+ end
164
+
165
+ # double-assignment to avoid warnings about variables that will be used by ERB
166
+ bin_path = bin_path = Carat.bin_path
167
+ template = template = File.read(File.expand_path('../templates/Executable', __FILE__))
168
+ relative_gemfile_path = relative_gemfile_path = Carat.default_gemfile.relative_path_from(bin_path)
169
+ ruby_command = ruby_command = Thor::Util.ruby_command
170
+
171
+ exists = []
172
+ spec.executables.each do |executable|
173
+ next if executable == "carat"
174
+
175
+ binstub_path = "#{bin_path}/#{executable}"
176
+ if File.exist?(binstub_path) && !options[:force]
177
+ exists << executable
178
+ next
179
+ end
180
+
181
+ File.open(binstub_path, 'w', 0777 & ~File.umask) do |f|
182
+ f.puts ERB.new(template, nil, '-').result(binding)
183
+ end
184
+ end
185
+
186
+ if options[:binstubs_cmd] && exists.any?
187
+ case exists.size
188
+ when 1
189
+ Carat.ui.warn "Skipped #{exists[0]} since it already exists."
190
+ when 2
191
+ Carat.ui.warn "Skipped #{exists.join(' and ')} since they already exist."
192
+ else
193
+ items = exists[0...-1].empty? ? nil : exists[0...-1].join(', ')
194
+ skipped = [items, exists[-1]].compact.join(' and ')
195
+ Carat.ui.warn "Skipped #{skipped} since they already exist."
196
+ end
197
+ Carat.ui.warn "If you want to overwrite skipped stubs, use --force."
198
+ end
199
+ end
200
+
201
+ private
202
+
203
+ def can_install_in_parallel?
204
+ if Carat.rubygems.provides?(">= 2.1.0")
205
+ true
206
+ else
207
+ Carat.ui.warn "Rubygems #{Gem::VERSION} is not threadsafe, so your "\
208
+ "gems must be installed one at a time. Upgrade to Rubygems 2.1.0 " \
209
+ "or higher to enable parallel gem installation."
210
+ false
211
+ end
212
+ end
213
+
214
+ def generate_standalone_carat_executable_stubs(spec)
215
+ # double-assignment to avoid warnings about variables that will be used by ERB
216
+ bin_path = Carat.bin_path
217
+ template = File.read(File.expand_path('../templates/Executable.standalone', __FILE__))
218
+ ruby_command = ruby_command = Thor::Util.ruby_command
219
+
220
+ spec.executables.each do |executable|
221
+ next if executable == "carat"
222
+ standalone_path = standalone_path = Pathname(Carat.settings[:path]).expand_path.relative_path_from(bin_path)
223
+ executable_path = executable_path = Pathname(spec.full_gem_path).join(spec.bindir, executable).relative_path_from(bin_path)
224
+ File.open "#{bin_path}/#{executable}", 'w', 0755 do |f|
225
+ f.puts ERB.new(template, nil, '-').result(binding)
226
+ end
227
+ end
228
+ end
229
+
230
+ def generate_standalone(groups)
231
+ standalone_path = Carat.settings[:path]
232
+ carat_path = File.join(standalone_path, "carat")
233
+ FileUtils.mkdir_p(carat_path)
234
+
235
+ paths = []
236
+
237
+ if groups.empty?
238
+ specs = @definition.requested_specs
239
+ else
240
+ specs = @definition.specs_for groups.map { |g| g.to_sym }
241
+ end
242
+
243
+ specs.each do |spec|
244
+ next if spec.name == "carat"
245
+ next if spec.require_paths.nil? # builtin gems
246
+
247
+ spec.require_paths.each do |path|
248
+ full_path = File.join(spec.full_gem_path, path)
249
+ gem_path = Pathname.new(full_path).relative_path_from(Carat.root.join(carat_path))
250
+ paths << gem_path.to_s.sub("#{Carat.ruby_version.engine}/#{RbConfig::CONFIG['ruby_version']}", '#{ruby_engine}/#{ruby_version}')
251
+ end
252
+ end
253
+
254
+
255
+ File.open File.join(carat_path, "setup.rb"), "w" do |file|
256
+ file.puts "require 'rbconfig'"
257
+ file.puts "# ruby 1.8.7 doesn't define RUBY_ENGINE"
258
+ file.puts "ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'"
259
+ file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]"
260
+ file.puts "path = File.expand_path('..', __FILE__)"
261
+ paths.each do |path|
262
+ file.puts %{$:.unshift "\#{path}/#{path}"}
263
+ end
264
+ end
265
+ end
266
+
267
+ def install_sequentially(standalone)
268
+ specs.each do |spec|
269
+ message = install_gem_from_spec spec, standalone, 0
270
+ if message
271
+ Installer.post_install_messages[spec.name] = message
272
+ end
273
+ end
274
+ end
275
+
276
+ def install_in_parallel(size, standalone)
277
+ name2spec = {}
278
+ remains = {}
279
+ enqueued = {}
280
+ specs.each do |spec|
281
+ name2spec[spec.name] = spec
282
+ remains[spec.name] = true
283
+ end
284
+
285
+ worker_pool = Worker.new size, lambda { |name, worker_num|
286
+ spec = name2spec[name]
287
+ message = install_gem_from_spec spec, standalone, worker_num
288
+ { :name => spec.name, :post_install => message }
289
+ }
290
+
291
+ # Keys in the remains hash represent uninstalled gems specs.
292
+ # We enqueue all gem specs that do not have any dependencies.
293
+ # Later we call this lambda again to install specs that depended on
294
+ # previously installed specifications. We continue until all specs
295
+ # are installed.
296
+ enqueue_remaining_specs = lambda do
297
+ remains.keys.each do |name|
298
+ next if enqueued[name]
299
+ spec = name2spec[name]
300
+ if ready_to_install?(spec, remains)
301
+ worker_pool.enq name
302
+ enqueued[name] = true
303
+ end
304
+ end
305
+ end
306
+ enqueue_remaining_specs.call
307
+
308
+ until remains.empty?
309
+ message = worker_pool.deq
310
+ remains.delete message[:name]
311
+ if message[:post_install]
312
+ Installer.post_install_messages[message[:name]] = message[:post_install]
313
+ end
314
+ enqueue_remaining_specs.call
315
+ end
316
+ message
317
+ ensure
318
+ worker_pool && worker_pool.stop
319
+ end
320
+
321
+ # We only want to install a gem spec if all its dependencies are met.
322
+ # If the dependency is no longer in the `remains` hash then it has been met.
323
+ # If a dependency is only development or is self referential it can be ignored.
324
+ def ready_to_install?(spec, remains)
325
+ spec.dependencies.none? do |dep|
326
+ next if dep.type == :development || dep.name == spec.name
327
+ remains[dep.name]
328
+ end
329
+ end
330
+
331
+ def create_bundle_path
332
+ Carat.mkdir_p(Carat.bundle_path.to_s) unless Carat.bundle_path.exist?
333
+ rescue Errno::EEXIST
334
+ raise PathError, "Could not install to path `#{Carat.settings[:path]}` " +
335
+ "because of an invalid symlink. Remove the symlink so the directory can be created."
336
+ end
337
+
338
+ end
339
+ end