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,83 @@
1
+ require "uri"
2
+ require "rubygems/spec_fetcher"
3
+ require "carat/match_platform"
4
+
5
+ module Carat
6
+ class LazySpecification
7
+ include MatchPlatform
8
+
9
+ attr_reader :name, :version, :dependencies, :platform
10
+ attr_accessor :source, :source_uri
11
+
12
+ def initialize(name, version, platform, source = nil)
13
+ @name = name
14
+ @version = version
15
+ @dependencies = []
16
+ @platform = platform
17
+ @source = source
18
+ @specification = nil
19
+ end
20
+
21
+ def full_name
22
+ if platform == Gem::Platform::RUBY or platform.nil? then
23
+ "#{@name}-#{@version}"
24
+ else
25
+ "#{@name}-#{@version}-#{platform}"
26
+ end
27
+ end
28
+
29
+ def ==(other)
30
+ identifier == other.identifier
31
+ end
32
+
33
+ def satisfies?(dependency)
34
+ @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
35
+ end
36
+
37
+ def to_lock
38
+ if platform == Gem::Platform::RUBY or platform.nil?
39
+ out = " #{name} (#{version})\n"
40
+ else
41
+ out = " #{name} (#{version}-#{platform})\n"
42
+ end
43
+
44
+ dependencies.sort_by {|d| d.to_s }.uniq.each do |dep|
45
+ next if dep.type == :development
46
+ out << " #{dep.to_lock}\n"
47
+ end
48
+
49
+ out
50
+ end
51
+
52
+ def __materialize__
53
+ @specification = source.specs.search(Gem::Dependency.new(name, version)).last
54
+ end
55
+
56
+ def respond_to?(*args)
57
+ super || @specification.respond_to?(*args)
58
+ end
59
+
60
+ def to_s
61
+ @__to_s ||= "#{name} (#{version})"
62
+ end
63
+
64
+ def identifier
65
+ @__identifier ||= [name, version, source, platform, dependencies].hash
66
+ end
67
+
68
+ private
69
+
70
+ def to_ary
71
+ nil
72
+ end
73
+
74
+ def method_missing(method, *args, &blk)
75
+ raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
76
+
77
+ return super unless respond_to?(method)
78
+
79
+ @specification.send(method, *args, &blk)
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,167 @@
1
+ require "strscan"
2
+
3
+ # Some versions of the Carat 1.1 RC series introduced corrupted
4
+ # lockfiles. There were two major problems:
5
+ #
6
+ # * multiple copies of the same GIT section appeared in the lockfile
7
+ # * when this happened, those sections got multiple copies of gems
8
+ # in those sections.
9
+ #
10
+ # As a result, Carat 1.1 contains code that fixes the earlier
11
+ # corruption. We will remove this fix-up code in Carat 1.2.
12
+
13
+ module Carat
14
+ class LockfileParser
15
+ attr_reader :sources, :dependencies, :specs, :platforms
16
+
17
+ DEPENDENCIES = "DEPENDENCIES"
18
+ PLATFORMS = "PLATFORMS"
19
+ GIT = "GIT"
20
+ GEM = "GEM"
21
+ PATH = "PATH"
22
+ SPECS = " specs:"
23
+ OPTIONS = /^ ([a-z]+): (.*)$/i
24
+ SOURCE = [GIT, GEM, PATH]
25
+
26
+ def initialize(lockfile)
27
+ @platforms = []
28
+ @sources = []
29
+ @dependencies = []
30
+ @state = nil
31
+ @specs = {}
32
+
33
+ @rubygems_aggregate = Source::Rubygems.new
34
+
35
+ if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
36
+ raise LockfileError, "Your Gemfile.lock contains merge conflicts.\n" \
37
+ "Run `git checkout HEAD -- Gemfile.lock` first to get a clean lock."
38
+ end
39
+
40
+ lockfile.split(/(?:\r?\n)+/).each do |line|
41
+ if SOURCE.include?(line)
42
+ @state = :source
43
+ parse_source(line)
44
+ elsif line == DEPENDENCIES
45
+ @state = :dependency
46
+ elsif line == PLATFORMS
47
+ @state = :platform
48
+ elsif line =~ /^[^\s]/
49
+ @state = nil
50
+ elsif @state
51
+ send("parse_#{@state}", line)
52
+ end
53
+ end
54
+ @sources << @rubygems_aggregate
55
+ @specs = @specs.values
56
+ rescue ArgumentError => e
57
+ Carat.ui.debug(e)
58
+ raise LockfileError, "Your lockfile is unreadable. Run `rm Gemfile.lock` " \
59
+ "and then `carat install` to generate a new lockfile."
60
+ end
61
+
62
+ private
63
+
64
+ TYPES = {
65
+ GIT => Carat::Source::Git,
66
+ GEM => Carat::Source::Rubygems,
67
+ PATH => Carat::Source::Path,
68
+ }
69
+
70
+ def parse_source(line)
71
+ case line
72
+ when GIT, GEM, PATH
73
+ @current_source = nil
74
+ @opts, @type = {}, line
75
+ when SPECS
76
+ case @type
77
+ when PATH
78
+ @current_source = TYPES[@type].from_lock(@opts)
79
+ @sources << @current_source
80
+ when GIT
81
+ @current_source = TYPES[@type].from_lock(@opts)
82
+ # Strip out duplicate GIT sections
83
+ if @sources.include?(@current_source)
84
+ @current_source = @sources.find { |s| s == @current_source }
85
+ else
86
+ @sources << @current_source
87
+ end
88
+ when GEM
89
+ Array(@opts["remote"]).each do |url|
90
+ @rubygems_aggregate.add_remote(url)
91
+ end
92
+ @current_source = @rubygems_aggregate
93
+ end
94
+ when OPTIONS
95
+ value = $2
96
+ value = true if value == "true"
97
+ value = false if value == "false"
98
+
99
+ key = $1
100
+
101
+ if @opts[key]
102
+ @opts[key] = Array(@opts[key])
103
+ @opts[key] << value
104
+ else
105
+ @opts[key] = value
106
+ end
107
+ else
108
+ parse_spec(line)
109
+ end
110
+ end
111
+
112
+ NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
113
+ NAME_VERSION_2 = %r{^ {2}#{NAME_VERSION}(!)?$}
114
+ NAME_VERSION_4 = %r{^ {4}#{NAME_VERSION}$}
115
+ NAME_VERSION_6 = %r{^ {6}#{NAME_VERSION}$}
116
+
117
+ def parse_dependency(line)
118
+ if line =~ NAME_VERSION_2
119
+ name, version, pinned = $1, $2, $4
120
+ version = version.split(",").map { |d| d.strip } if version
121
+
122
+ dep = Carat::Dependency.new(name, version)
123
+
124
+ if pinned && dep.name != 'carat'
125
+ spec = @specs.find {|k, v| v.name == dep.name }
126
+ dep.source = spec.last.source if spec
127
+
128
+ # Path sources need to know what the default name / version
129
+ # to use in the case that there are no gemspecs present. A fake
130
+ # gemspec is created based on the version set on the dependency
131
+ # TODO: Use the version from the spec instead of from the dependency
132
+ if version && version.size == 1 && version.first =~ /^\s*= (.+)\s*$/ && dep.source.is_a?(Carat::Source::Path)
133
+ dep.source.name = name
134
+ dep.source.version = $1
135
+ end
136
+ end
137
+
138
+ @dependencies << dep
139
+ end
140
+ end
141
+
142
+ def parse_spec(line)
143
+ if line =~ NAME_VERSION_4
144
+ name, version = $1, Gem::Version.new($2)
145
+ platform = $3 ? Gem::Platform.new($3) : Gem::Platform::RUBY
146
+ @current_spec = LazySpecification.new(name, version, platform)
147
+ @current_spec.source = @current_source
148
+
149
+ # Avoid introducing multiple copies of the same spec (caused by
150
+ # duplicate GIT sections)
151
+ @specs[@current_spec.identifier] ||= @current_spec
152
+ elsif line =~ NAME_VERSION_6
153
+ name, version = $1, $2
154
+ version = version.split(',').map { |d| d.strip } if version
155
+ dep = Gem::Dependency.new(name, version)
156
+ @current_spec.dependencies << dep
157
+ end
158
+ end
159
+
160
+ def parse_platform(line)
161
+ if line =~ /^ (.*)$/
162
+ @platforms << Gem::Platform.new($1)
163
+ end
164
+ end
165
+
166
+ end
167
+ end
@@ -0,0 +1,13 @@
1
+ require 'carat/gem_helpers'
2
+
3
+ module Carat
4
+ module MatchPlatform
5
+ include GemHelpers
6
+
7
+ def match_platform(p)
8
+ Gem::Platform::RUBY == platform or
9
+ platform.nil? or p == platform or
10
+ generic(Gem::Platform.new(platform)) === p
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # Psych could be a gem, so try to ask for it
2
+ begin
3
+ gem 'psych'
4
+ rescue LoadError
5
+ end if defined?(gem)
6
+
7
+ # Psych could just be in the stdlib
8
+ # but it's too late if Syck is already loaded
9
+ begin
10
+ require 'psych' unless defined?(Syck)
11
+ rescue LoadError
12
+ # Apparently Psych wasn't available. Oh well.
13
+ end
14
+
15
+ # At least load the YAML stdlib, whatever that may be
16
+ require 'yaml' unless defined?(YAML.dump)
17
+
18
+ module Carat
19
+ # On encountering invalid YAML,
20
+ # Psych raises Psych::SyntaxError
21
+ if defined?(::Psych::SyntaxError)
22
+ YamlSyntaxError = ::Psych::SyntaxError
23
+ else # Syck raises ArgumentError
24
+ YamlSyntaxError = ::ArgumentError
25
+ end
26
+ end
@@ -0,0 +1,57 @@
1
+ require "uri"
2
+ require "rubygems/spec_fetcher"
3
+
4
+ module Carat
5
+ # Represents a lazily loaded gem specification, where the full specification
6
+ # is on the source server in rubygems' "quick" index. The proxy object is to
7
+ # be seeded with what we're given from the source's abbreviated index - the
8
+ # full specification will only be fetched when necessary.
9
+ class RemoteSpecification
10
+ include MatchPlatform
11
+
12
+ attr_reader :name, :version, :platform
13
+ attr_accessor :source, :source_uri
14
+
15
+ def initialize(name, version, platform, spec_fetcher)
16
+ @name = name
17
+ @version = version
18
+ @platform = platform
19
+ @spec_fetcher = spec_fetcher
20
+ end
21
+
22
+ # Needed before installs, since the arch matters then and quick
23
+ # specs don't bother to include the arch in the platform string
24
+ def fetch_platform
25
+ @platform = _remote_specification.platform
26
+ end
27
+
28
+ def full_name
29
+ if platform == Gem::Platform::RUBY or platform.nil? then
30
+ "#{@name}-#{@version}"
31
+ else
32
+ "#{@name}-#{@version}-#{platform}"
33
+ end
34
+ end
35
+
36
+ # Because Rubyforge cannot be trusted to provide valid specifications
37
+ # once the remote gem is downloaded, the backend specification will
38
+ # be swapped out.
39
+ def __swap__(spec)
40
+ @specification = spec
41
+ end
42
+
43
+ private
44
+
45
+ def _remote_specification
46
+ @specification ||= @spec_fetcher.fetch_spec([@name, @version, @platform])
47
+ end
48
+
49
+ def method_missing(method, *args, &blk)
50
+ if Gem::Specification.new.respond_to?(method)
51
+ _remote_specification.send(method, *args, &blk)
52
+ else
53
+ super
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,334 @@
1
+ require 'set'
2
+
3
+ # This is the latest iteration of the gem dependency resolving algorithm. As of now,
4
+ # it can resolve (as a success or failure) any set of gem dependencies we throw at it
5
+ # in a reasonable amount of time. The most iterations I've seen it take is about 150.
6
+ # The actual implementation of the algorithm is not as good as it could be yet, but that
7
+ # can come later.
8
+
9
+ module Carat
10
+ class Resolver
11
+
12
+ require 'carat/vendored_molinillo'
13
+
14
+ class Molinillo::VersionConflict
15
+ def clean_req(req)
16
+ if req.to_s.include?(">= 0")
17
+ req.to_s.gsub(/ \(.*?\)$/, '')
18
+ else
19
+ req.to_s.gsub(/\, (runtime|development)\)$/, ')')
20
+ end
21
+ end
22
+
23
+ def message
24
+ conflicts.values.flatten.reduce('') do |o, conflict|
25
+ o << %(Carat could not find compatible versions for gem "#{conflict.requirement.name}":\n)
26
+ if conflict.locked_requirement
27
+ o << %( In snapshot (Gemfile.lock):\n)
28
+ o << %( #{clean_req conflict.locked_requirement}\n)
29
+ o << %(\n)
30
+ end
31
+ o << %( In Gemfile:\n)
32
+ o << conflict.requirement_trees.map do |tree|
33
+ t = ''
34
+ depth = 2
35
+ tree.each do |req|
36
+ t << ' ' * depth << %(#{clean_req req})
37
+ t << %( depends on) unless tree[-1] == req
38
+ t << %(\n)
39
+ depth += 1
40
+ end
41
+ t
42
+ end.join("\n")
43
+
44
+ if conflict.requirement.name == 'carat'
45
+ o << %(\n Current Carat version:\n carat (#{Carat::VERSION}))
46
+ other_carat_required = !conflict.requirement.requirement.satisfied_by?(Gem::Version.new Carat::VERSION)
47
+ end
48
+
49
+ if conflict.requirement.name == "carat" && other_carat_required
50
+ o << "\n"
51
+ o << "This Gemfile requires a different version of Carat.\n"
52
+ o << "Perhaps you need to update Carat by running `gem install carat`?\n"
53
+ end
54
+ if conflict.locked_requirement
55
+ o << "\n"
56
+ o << %(Running `carat update` will rebuild your snapshot from scratch, using only\n)
57
+ o << %(the gems in your Gemfile, which may resolve the conflict.\n)
58
+ elsif !conflict.existing
59
+ if conflict.requirement_trees.first.size > 1
60
+ o << "Could not find gem '#{clean_req(conflict.requirement)}', which is required by "
61
+ o << "gem '#{clean_req(conflict.requirement_trees.first[-2])}', in any of the sources."
62
+ else
63
+ o << "Could not find gem '#{clean_req(conflict.requirement)} in any of the sources\n"
64
+ end
65
+ end
66
+ o
67
+ end
68
+ end
69
+ end
70
+
71
+ ALL = Carat::Dependency::PLATFORM_MAP.values.uniq.freeze
72
+
73
+ class SpecGroup < Array
74
+ include GemHelpers
75
+
76
+ attr_reader :activated, :required_by
77
+
78
+ def initialize(a)
79
+ super
80
+ @required_by = []
81
+ @activated = []
82
+ @dependencies = nil
83
+ @specs = {}
84
+
85
+ ALL.each do |p|
86
+ @specs[p] = reverse.find { |s| s.match_platform(p) }
87
+ end
88
+ end
89
+
90
+ def initialize_copy(o)
91
+ super
92
+ @required_by = o.required_by.dup
93
+ @activated = o.activated.dup
94
+ end
95
+
96
+ def to_specs
97
+ specs = {}
98
+
99
+ @activated.each do |p|
100
+ if s = @specs[p]
101
+ platform = generic(Gem::Platform.new(s.platform))
102
+ next if specs[platform]
103
+
104
+ lazy_spec = LazySpecification.new(name, version, platform, source)
105
+ lazy_spec.dependencies.replace s.dependencies
106
+ specs[platform] = lazy_spec
107
+ end
108
+ end
109
+ specs.values
110
+ end
111
+
112
+ def activate_platform(platform)
113
+ unless @activated.include?(platform)
114
+ if for?(platform)
115
+ @activated << platform
116
+ return __dependencies[platform] || []
117
+ end
118
+ end
119
+ []
120
+ end
121
+
122
+ def name
123
+ @name ||= first.name
124
+ end
125
+
126
+ def version
127
+ @version ||= first.version
128
+ end
129
+
130
+ def source
131
+ @source ||= first.source
132
+ end
133
+
134
+ def for?(platform)
135
+ @specs[platform]
136
+ end
137
+
138
+ def to_s
139
+ "#{name} (#{version})"
140
+ end
141
+
142
+ def dependencies_for_activated_platforms
143
+ @activated.map { |p| __dependencies[p] }.flatten
144
+ end
145
+
146
+ def platforms_for_dependency_named(dependency)
147
+ __dependencies.select { |p, deps| deps.map(&:name).include? dependency }.keys
148
+ end
149
+
150
+ private
151
+
152
+ def __dependencies
153
+ @dependencies ||= begin
154
+ dependencies = {}
155
+ ALL.each do |p|
156
+ if spec = @specs[p]
157
+ dependencies[p] = []
158
+ spec.dependencies.each do |dep|
159
+ next if dep.type == :development
160
+ dependencies[p] << DepProxy.new(dep, p)
161
+ end
162
+ end
163
+ end
164
+ dependencies
165
+ end
166
+ end
167
+ end
168
+
169
+ # Figures out the best possible configuration of gems that satisfies
170
+ # the list of passed dependencies and any child dependencies without
171
+ # causing any gem activation errors.
172
+ #
173
+ # ==== Parameters
174
+ # *dependencies<Gem::Dependency>:: The list of dependencies to resolve
175
+ #
176
+ # ==== Returns
177
+ # <GemBundle>,nil:: If the list of dependencies can be resolved, a
178
+ # collection of gemspecs is returned. Otherwise, nil is returned.
179
+ def self.resolve(requirements, index, source_requirements = {}, base = [])
180
+ base = SpecSet.new(base) unless base.is_a?(SpecSet)
181
+ resolver = new(index, source_requirements, base)
182
+ result = resolver.start(requirements)
183
+ SpecSet.new(result)
184
+ end
185
+
186
+
187
+ def initialize(index, source_requirements, base)
188
+ @index = index
189
+ @source_requirements = source_requirements
190
+ @base = base
191
+ @resolver = Molinillo::Resolver.new(self, self)
192
+ @search_for = {}
193
+ @prereleases_cache = Hash.new { |h,k| h[k] = k.prerelease? }
194
+ @base_dg = Molinillo::DependencyGraph.new
195
+ @base.each { |ls| @base_dg.add_root_vertex ls.name, Dependency.new(ls.name, ls.version) }
196
+ end
197
+
198
+ def start(requirements)
199
+ verify_gemfile_dependencies_are_found!(requirements)
200
+ dg = @resolver.resolve(requirements, @base_dg)
201
+ dg.map(&:payload).map(&:to_specs).flatten
202
+ rescue Molinillo::VersionConflict => e
203
+ raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
204
+ rescue Molinillo::CircularDependencyError => e
205
+ names = e.dependencies.sort_by(&:name).map { |d| "gem '#{d.name}'"}
206
+ raise CyclicDependencyError, "Your Gemfile requires gems that depend" \
207
+ " on each other, creating an infinite loop. Please remove" \
208
+ " #{names.count > 1 ? 'either ' : '' }#{names.join(' or ')}" \
209
+ " and try again."
210
+ end
211
+
212
+ include Molinillo::UI
213
+
214
+ # Conveys debug information to the user.
215
+ #
216
+ # @param [Integer] depth the current depth of the resolution process.
217
+ # @return [void]
218
+ def debug(depth = 0)
219
+ if debug?
220
+ debug_info = yield
221
+ debug_info = debug_info.inspect unless debug_info.is_a?(String)
222
+ STDERR.puts debug_info.split("\n").map { |s| ' ' * depth + s }
223
+ end
224
+ end
225
+
226
+ def debug?
227
+ ENV['DEBUG_RESOLVER'] || ENV['DEBUG_RESOLVER_TREE']
228
+ end
229
+
230
+ def before_resolution
231
+ Carat.ui.info 'Resolving dependencies...', false
232
+ end
233
+
234
+ def after_resolution
235
+ Carat.ui.info ''
236
+ end
237
+
238
+ def indicate_progress
239
+ Carat.ui.info '.', false
240
+ end
241
+
242
+ private
243
+
244
+ include Molinillo::SpecificationProvider
245
+
246
+ def dependencies_for(specification)
247
+ specification.dependencies_for_activated_platforms
248
+ end
249
+
250
+ def search_for(dependency)
251
+ platform = dependency.__platform
252
+ dependency = dependency.dep unless dependency.is_a? Gem::Dependency
253
+ search = @search_for[dependency] ||= begin
254
+ index = @source_requirements[dependency.name] || @index
255
+ results = index.search(dependency, @base[dependency.name])
256
+ if vertex = @base_dg.vertex_named(dependency.name)
257
+ locked_requirement = vertex.payload.requirement
258
+ end
259
+ if results.any?
260
+ version = results.first.version
261
+ nested = [[]]
262
+ results.each do |spec|
263
+ if spec.version != version
264
+ nested << []
265
+ version = spec.version
266
+ end
267
+ nested.last << spec
268
+ end
269
+ groups = nested.map { |a| SpecGroup.new(a) }
270
+ !locked_requirement ? groups : groups.select { |sg| locked_requirement.satisfied_by? sg.version }
271
+ else
272
+ []
273
+ end
274
+ end
275
+ search.select { |sg| sg.for?(platform) }.each { |sg| sg.activate_platform(platform) }
276
+ end
277
+
278
+ def name_for(dependency)
279
+ dependency.name
280
+ end
281
+
282
+ def name_for_explicit_dependency_source
283
+ 'Gemfile'
284
+ end
285
+
286
+ def name_for_locking_dependency_source
287
+ 'Gemfile.lock'
288
+ end
289
+
290
+ def requirement_satisfied_by?(requirement, activated, spec)
291
+ requirement.matches_spec?(spec)
292
+ end
293
+
294
+ def sort_dependencies(dependencies, activated, conflicts)
295
+ dependencies.sort_by do |dependency|
296
+ name = name_for(dependency)
297
+ [
298
+ activated.vertex_named(name).payload ? 0 : 1,
299
+ @prereleases_cache[dependency.requirement] ? 0 : 1,
300
+ conflicts[name] ? 0 : 1,
301
+ activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
302
+ ]
303
+ end
304
+ end
305
+
306
+ def verify_gemfile_dependencies_are_found!(requirements)
307
+ requirements.each do |requirement|
308
+ next if requirement.name == 'carat'
309
+ if search_for(requirement).empty?
310
+ if base = @base[requirement.name] and !base.empty?
311
+ version = base.first.version
312
+ message = "You have requested:\n" \
313
+ " #{requirement.name} #{requirement.requirement}\n\n" \
314
+ "The bundle currently has #{requirement.name} locked at #{version}.\n" \
315
+ "Try running `carat update #{requirement.name}`"
316
+ elsif requirement.source
317
+ name = requirement.name
318
+ versions = @source_requirements[name][name].map { |s| s.version }
319
+ message = "Could not find gem '#{requirement}' in #{requirement.source}.\n"
320
+ if versions.any?
321
+ message << "Source contains '#{name}' at: #{versions.join(', ')}"
322
+ else
323
+ message << "Source does not contain any versions of '#{requirement}'"
324
+ end
325
+ else
326
+ message = "Could not find gem '#{requirement}' in any of the gem sources listed in your Gemfile or installed on this machine."
327
+ end
328
+ raise GemNotFound, message
329
+ end
330
+ end
331
+ end
332
+
333
+ end
334
+ end