carat 1.9.9.pre1

Sign up to get free protection for your applications and to get access to all the features.
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,73 @@
1
+ module Carat
2
+ class CLI::Update
3
+ attr_reader :options, :gems
4
+ def initialize(options, gems)
5
+ @options = options
6
+ @gems = gems
7
+ end
8
+
9
+ def run
10
+ Carat.ui.level = "error" if options[:quiet]
11
+
12
+ sources = Array(options[:source])
13
+ groups = Array(options[:group]).map(&:to_sym)
14
+
15
+ if gems.empty? && sources.empty? && groups.empty?
16
+ # We're doing a full update
17
+ Carat.definition(true)
18
+ else
19
+ unless Carat.default_lockfile.exist?
20
+ raise GemfileLockNotFound, "This Bundle hasn't been installed yet. " \
21
+ "Run `carat install` to update and install the bundled gems."
22
+ end
23
+ # cycle through the requested gems, just to make sure they exist
24
+ names = Carat.locked_gems.specs.map{ |s| s.name }
25
+ gems.each do |g|
26
+ next if names.include?(g)
27
+ require "carat/cli/common"
28
+ raise GemNotFound, Carat::CLI::Common.gem_not_found_message(g, names)
29
+ end
30
+
31
+ if groups.any?
32
+ specs = Carat.definition.specs_for groups
33
+ sources.concat(specs.map(&:name))
34
+ end
35
+
36
+ Carat.definition(:gems => gems, :sources => sources)
37
+ end
38
+
39
+ Carat::Fetcher.disable_endpoint = options["full-index"]
40
+
41
+ opts = options.dup
42
+ opts["update"] = true
43
+ opts["local"] = options[:local]
44
+
45
+ Carat.settings[:jobs] = opts["jobs"] if opts["jobs"]
46
+
47
+ # rubygems plugins sometimes hook into the gem install process
48
+ Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins)
49
+
50
+ Carat.definition.validate_ruby!
51
+ Installer.install Carat.root, Carat.definition, opts
52
+ Carat.load.cache if Carat.app_cache.exist?
53
+
54
+ if Carat.settings[:clean] && Carat.settings[:path]
55
+ require "carat/cli/clean"
56
+ Carat::CLI::Clean.new(options).run
57
+ end
58
+
59
+ Carat.ui.confirm "Bundle updated!"
60
+ without_groups_messages
61
+ end
62
+
63
+ private
64
+
65
+ def without_groups_messages
66
+ if Carat.settings.without.any?
67
+ require "carat/cli/common"
68
+ Carat.ui.confirm Carat::CLI::Common.without_groups_message
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ module Carat
2
+ class CLI::Viz
3
+ attr_reader :options, :gem_name
4
+ def initialize(options)
5
+ @options = options
6
+ end
7
+
8
+ def run
9
+ require 'graphviz'
10
+ output_file = File.expand_path(options[:file])
11
+ graph = Graph.new(Carat.load, output_file, options[:version], options[:requirements], options[:format], options[:without])
12
+ graph.viz
13
+ rescue LoadError => e
14
+ Carat.ui.error e.inspect
15
+ Carat.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
16
+ Carat.ui.warn "`gem install ruby-graphviz`"
17
+ rescue StandardError => e
18
+ if e.message =~ /GraphViz not installed or dot not in PATH/
19
+ Carat.ui.error e.message
20
+ Carat.ui.warn "Please install GraphViz. On a Mac with homebrew, you can run `brew install graphviz`."
21
+ else
22
+ raise
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Carat
2
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)!
3
+ FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
4
+ NULL = WINDOWS ? "NUL" : "/dev/null"
5
+ end
@@ -0,0 +1,183 @@
1
+ module Carat
2
+ # Returns current version of Ruby
3
+ #
4
+ # @return [CurrentRuby] Current version of Ruby
5
+ def self.current_ruby
6
+ @current_ruby ||= CurrentRuby.new
7
+ end
8
+
9
+ class CurrentRuby
10
+ def on_18?
11
+ RUBY_VERSION =~ /^1\.8/
12
+ end
13
+
14
+ def on_19?
15
+ RUBY_VERSION =~ /^1\.9/
16
+ end
17
+
18
+ def on_20?
19
+ RUBY_VERSION =~ /^2\.0/
20
+ end
21
+
22
+ def on_21?
23
+ RUBY_VERSION =~ /^2\.1/
24
+ end
25
+
26
+ def on_22?
27
+ RUBY_VERSION =~ /^2\.2/
28
+ end
29
+
30
+ def ruby?
31
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
32
+ end
33
+
34
+ def ruby_18?
35
+ ruby? && on_18?
36
+ end
37
+
38
+ def ruby_19?
39
+ ruby? && on_19?
40
+ end
41
+
42
+ def ruby_20?
43
+ ruby? && on_20?
44
+ end
45
+
46
+ def ruby_21?
47
+ ruby? && on_21?
48
+ end
49
+
50
+ def ruby_22?
51
+ ruby? && on_22?
52
+ end
53
+
54
+ def mri?
55
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
56
+ end
57
+
58
+ def mri_18?
59
+ mri? && on_18?
60
+ end
61
+
62
+ def mri_19?
63
+ mri? && on_19?
64
+ end
65
+
66
+ def mri_20?
67
+ mri? && on_20?
68
+ end
69
+
70
+ def mri_21?
71
+ mri? && on_21?
72
+ end
73
+
74
+ def mri_22?
75
+ mri? && on_22?
76
+ end
77
+
78
+ def rbx?
79
+ ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
80
+ end
81
+
82
+ def jruby?
83
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
84
+ end
85
+
86
+ def jruby_18?
87
+ jruby? && on_18?
88
+ end
89
+
90
+ def jruby_19?
91
+ jruby? && on_19?
92
+ end
93
+
94
+ def maglev?
95
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
96
+ end
97
+
98
+ def mswin?
99
+ Carat::WINDOWS
100
+ end
101
+
102
+ def mswin_18?
103
+ mswin? && on_18?
104
+ end
105
+
106
+ def mswin_19?
107
+ mswin? && on_19?
108
+ end
109
+
110
+ def mswin_20?
111
+ mswin? && on_20?
112
+ end
113
+
114
+ def mswin_21?
115
+ mswin? && on_21?
116
+ end
117
+
118
+ def mswin_22?
119
+ mswin? && on_22?
120
+ end
121
+
122
+ def mswin64?
123
+ Carat::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == 'x64'
124
+ end
125
+
126
+ def mswin64_19?
127
+ mswin64? && on_19?
128
+ end
129
+
130
+ def mswin64_20?
131
+ mswin64? && on_20?
132
+ end
133
+
134
+ def mswin64_21?
135
+ mswin64? && on_21?
136
+ end
137
+
138
+ def mswin64_22?
139
+ mswin64? && on_22?
140
+ end
141
+
142
+ def mingw?
143
+ Carat::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != 'x64'
144
+ end
145
+
146
+ def mingw_18?
147
+ mingw? && on_18?
148
+ end
149
+
150
+ def mingw_19?
151
+ mingw? && on_19?
152
+ end
153
+
154
+ def mingw_20?
155
+ mingw? && on_20?
156
+ end
157
+
158
+ def mingw_21?
159
+ mingw? && on_21?
160
+ end
161
+
162
+ def mingw_22?
163
+ mingw? && on_22?
164
+ end
165
+
166
+ def x64_mingw?
167
+ Carat::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu == 'x64'
168
+ end
169
+
170
+ def x64_mingw_20?
171
+ x64_mingw? && on_20?
172
+ end
173
+
174
+ def x64_mingw_21?
175
+ x64_mingw? && on_21?
176
+ end
177
+
178
+ def x64_mingw_22?
179
+ x64_mingw? && on_22?
180
+ end
181
+
182
+ end
183
+ end
@@ -0,0 +1,628 @@
1
+ require "digest/sha1"
2
+ require "set"
3
+
4
+ module Carat
5
+ class Definition
6
+ include GemHelpers
7
+
8
+ attr_reader :dependencies, :platforms, :ruby_version, :locked_deps
9
+
10
+ # Given a gemfile and lockfile creates a Carat definition
11
+ #
12
+ # @param gemfile [Pathname] Path to Gemfile
13
+ # @param lockfile [Pathname,nil] Path to Gemfile.lock
14
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
15
+ # to be updated or true if all gems should be updated
16
+ # @return [Carat::Definition]
17
+ def self.build(gemfile, lockfile, unlock)
18
+ unlock ||= {}
19
+ gemfile = Pathname.new(gemfile).expand_path
20
+
21
+ unless gemfile.file?
22
+ raise GemfileNotFound, "#{gemfile} not found"
23
+ end
24
+
25
+ Dsl.evaluate(gemfile, lockfile, unlock)
26
+ end
27
+
28
+
29
+ #
30
+ # How does the new system work?
31
+ #
32
+ # * Load information from Gemfile and Lockfile
33
+ # * Invalidate stale locked specs
34
+ # * All specs from stale source are stale
35
+ # * All specs that are reachable only through a stale
36
+ # dependency are stale.
37
+ # * If all fresh dependencies are satisfied by the locked
38
+ # specs, then we can try to resolve locally.
39
+ #
40
+ # @param lockfile [Pathname] Path to Gemfile.lock
41
+ # @param dependencies [Array(Carat::Dependency)] array of dependencies from Gemfile
42
+ # @param sources [Carat::SourceList]
43
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
44
+ # to be updated or true if all gems should be updated
45
+ # @param ruby_version [Carat::RubyVersion, nil] Requested Ruby Version
46
+ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil)
47
+ @unlocking = unlock == true || !unlock.empty?
48
+
49
+ @dependencies, @sources, @unlock = dependencies, sources, unlock
50
+ @remote = false
51
+ @specs = nil
52
+ @lockfile_contents = ""
53
+ @ruby_version = ruby_version
54
+
55
+ if lockfile && File.exist?(lockfile)
56
+ @lockfile_contents = Carat.read_file(lockfile)
57
+ locked = LockfileParser.new(@lockfile_contents)
58
+ @platforms = locked.platforms
59
+
60
+ if unlock != true
61
+ @locked_deps = locked.dependencies
62
+ @locked_specs = SpecSet.new(locked.specs)
63
+ @locked_sources = locked.sources
64
+ else
65
+ @unlock = {}
66
+ @locked_deps = []
67
+ @locked_specs = SpecSet.new([])
68
+ @locked_sources = []
69
+ end
70
+ else
71
+ @unlock = {}
72
+ @platforms = []
73
+ @locked_deps = []
74
+ @locked_specs = SpecSet.new([])
75
+ @locked_sources = []
76
+ end
77
+
78
+ @unlock[:gems] ||= []
79
+ @unlock[:sources] ||= []
80
+
81
+ current_platform = Carat.rubygems.platforms.map { |p| generic(p) }.compact.last
82
+ @new_platform = !@platforms.include?(current_platform)
83
+ @platforms |= [current_platform]
84
+
85
+ @path_changes = converge_paths
86
+ eager_unlock = expand_dependencies(@unlock[:gems])
87
+ @unlock[:gems] = @locked_specs.for(eager_unlock).map { |s| s.name }
88
+
89
+ @source_changes = converge_sources
90
+ @dependency_changes = converge_dependencies
91
+ @local_changes = converge_locals
92
+
93
+ fixup_dependency_types!
94
+ end
95
+
96
+ def fixup_dependency_types!
97
+ # XXX This is a temporary workaround for a bug when using rubygems 1.8.15
98
+ # where Gem::Dependency#== matches Gem::Dependency#type. As the lockfile
99
+ # doesn't carry a notion of the dependency type, if you use
100
+ # add_development_dependency in a gemspec that's loaded with the gemspec
101
+ # directive, the lockfile dependencies and resolved dependencies end up
102
+ # with a mismatch on #type.
103
+ # Test coverage to catch a regression on this is in gemspec_spec.rb
104
+ @dependencies.each do |d|
105
+ if ld = @locked_deps.find { |l| l.name == d.name }
106
+ ld.instance_variable_set(:@type, d.type)
107
+ end
108
+ end
109
+ end
110
+
111
+ def resolve_with_cache!
112
+ raise "Specs already loaded" if @specs
113
+ sources.cached!
114
+ specs
115
+ end
116
+
117
+ def resolve_remotely!
118
+ raise "Specs already loaded" if @specs
119
+ @remote = true
120
+ sources.remote!
121
+ specs
122
+ end
123
+
124
+ # For given dependency list returns a SpecSet with Gemspec of all the required
125
+ # dependencies.
126
+ # 1. The method first resolves the dependencies specified in Gemfile
127
+ # 2. After that it tries and fetches gemspec of resolved dependencies
128
+ #
129
+ # @return [Carat::SpecSet]
130
+ def specs
131
+ @specs ||= begin
132
+ specs = resolve.materialize(Carat.settings[:cache_all_platforms] ? dependencies : requested_dependencies)
133
+
134
+ unless specs["carat"].any?
135
+ local = Carat.settings[:frozen] ? rubygems_index : index
136
+ carat = local.search(Gem::Dependency.new('carat', VERSION)).last
137
+ specs["carat"] = carat if carat
138
+ end
139
+
140
+ specs
141
+ end
142
+ end
143
+
144
+ def new_specs
145
+ specs - @locked_specs
146
+ end
147
+
148
+ def removed_specs
149
+ @locked_specs - specs
150
+ end
151
+
152
+ def new_platform?
153
+ @new_platform
154
+ end
155
+
156
+ def missing_specs
157
+ missing = []
158
+ resolve.materialize(requested_dependencies, missing)
159
+ missing
160
+ end
161
+
162
+ def requested_specs
163
+ @requested_specs ||= begin
164
+ groups = self.groups - Carat.settings.without
165
+ groups.map! { |g| g.to_sym }
166
+ specs_for(groups)
167
+ end
168
+ end
169
+
170
+ def current_dependencies
171
+ dependencies.reject { |d| !d.should_include? }
172
+ end
173
+
174
+ def specs_for(groups)
175
+ deps = dependencies.select { |d| (d.groups & groups).any? }
176
+ deps.delete_if { |d| !d.should_include? }
177
+ specs.for(expand_dependencies(deps))
178
+ end
179
+
180
+ # Resolve all the dependencies specified in Gemfile. It ensures that
181
+ # dependencies that have been already resolved via locked file and are fresh
182
+ # are reused when resolving dependencies
183
+ #
184
+ # @return [SpecSet] resolved dependencies
185
+ def resolve
186
+ @resolve ||= begin
187
+ last_resolve = converge_locked_specs
188
+ if Carat.settings[:frozen] || (!@unlocking && nothing_changed?)
189
+ last_resolve
190
+ else
191
+ # Run a resolve against the locally available gems
192
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve)
193
+ end
194
+ end
195
+ end
196
+
197
+ def index
198
+ @index ||= Index.build do |idx|
199
+ dependency_names = @dependencies.map { |d| d.name }
200
+
201
+ sources.all_sources.each do |source|
202
+ source.dependency_names = dependency_names.dup
203
+ idx.add_source source.specs
204
+ dependency_names -= pinned_spec_names(source.specs)
205
+ dependency_names.push(*source.unmet_deps).uniq!
206
+ end
207
+ end
208
+ end
209
+
210
+ # used when frozen is enabled so we can find the carat
211
+ # spec, even if (say) a git gem is not checked out.
212
+ def rubygems_index
213
+ @rubygems_index ||= Index.build do |idx|
214
+ sources.rubygems_sources.each do |rubygems|
215
+ idx.add_source rubygems.specs
216
+ end
217
+ end
218
+ end
219
+
220
+ def has_rubygems_remotes?
221
+ sources.rubygems_sources.any? {|s| s.remotes.any? }
222
+ end
223
+
224
+ def has_local_dependencies?
225
+ !sources.path_sources.empty? || !sources.git_sources.empty?
226
+ end
227
+
228
+ def spec_git_paths
229
+ sources.git_sources.map {|s| s.path.to_s }
230
+ end
231
+
232
+ def groups
233
+ dependencies.map { |d| d.groups }.flatten.uniq
234
+ end
235
+
236
+ def lock(file)
237
+ contents = to_lock
238
+
239
+ # Convert to \r\n if the existing lock has them
240
+ # i.e., Windows with `git config core.autocrlf=true`
241
+ contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match("\r\n")
242
+
243
+ return if @lockfile_contents == contents
244
+
245
+ if Carat.settings[:frozen]
246
+ Carat.ui.error "Cannot write a changed lockfile while frozen."
247
+ return
248
+ end
249
+
250
+ File.open(file, 'wb'){|f| f.puts(contents) }
251
+ rescue Errno::EACCES
252
+ raise Carat::InstallError,
253
+ "There was an error while trying to write to Gemfile.lock. It is likely that \n" \
254
+ "you need to allow write permissions for the file at path: \n" \
255
+ "#{File.expand_path(file)}"
256
+ end
257
+
258
+ def to_lock
259
+ out = ""
260
+
261
+ sources.lock_sources.each do |source|
262
+ # Add the source header
263
+ out << source.to_lock
264
+ # Find all specs for this source
265
+ resolve.
266
+ select { |s| source.can_lock?(s) }.
267
+ # This needs to be sorted by full name so that
268
+ # gems with the same name, but different platform
269
+ # are ordered consistently
270
+ sort_by { |s| s.full_name }.
271
+ each do |spec|
272
+ next if spec.name == 'carat'
273
+ out << spec.to_lock
274
+ end
275
+ out << "\n"
276
+ end
277
+
278
+ out << "PLATFORMS\n"
279
+
280
+ platforms.map { |p| p.to_s }.sort.each do |p|
281
+ out << " #{p}\n"
282
+ end
283
+
284
+ out << "\n"
285
+ out << "DEPENDENCIES\n"
286
+
287
+ handled = []
288
+ dependencies.
289
+ sort_by { |d| d.to_s }.
290
+ each do |dep|
291
+ next if handled.include?(dep.name)
292
+ out << dep.to_lock
293
+ handled << dep.name
294
+ end
295
+
296
+ out
297
+ end
298
+
299
+ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
300
+ msg = "You are trying to install in deployment mode after changing\n" \
301
+ "your Gemfile. Run `carat install` elsewhere and add the\n" \
302
+ "updated Gemfile.lock to version control."
303
+
304
+ unless explicit_flag
305
+ msg += "\n\nIf this is a development machine, remove the Gemfile " \
306
+ "freeze \nby running `carat install --no-deployment`."
307
+ end
308
+
309
+ added = []
310
+ deleted = []
311
+ changed = []
312
+
313
+ gemfile_sources = sources.lock_sources
314
+ if @locked_sources != gemfile_sources
315
+ new_sources = gemfile_sources - @locked_sources
316
+ deleted_sources = @locked_sources - gemfile_sources
317
+
318
+ if new_sources.any?
319
+ added.concat new_sources.map { |source| "* source: #{source}" }
320
+ end
321
+
322
+ if deleted_sources.any?
323
+ deleted.concat deleted_sources.map { |source| "* source: #{source}" }
324
+ end
325
+ end
326
+
327
+ new_deps = @dependencies - @locked_deps
328
+ deleted_deps = @locked_deps - @dependencies
329
+
330
+ if new_deps.any?
331
+ added.concat new_deps.map { |d| "* #{pretty_dep(d)}" }
332
+ end
333
+
334
+ if deleted_deps.any?
335
+ deleted.concat deleted_deps.map { |d| "* #{pretty_dep(d)}" }
336
+ end
337
+
338
+ both_sources = Hash.new { |h,k| h[k] = [] }
339
+ @dependencies.each { |d| both_sources[d.name][0] = d }
340
+ @locked_deps.each { |d| both_sources[d.name][1] = d.source }
341
+
342
+ both_sources.each do |name, (dep, lock_source)|
343
+ if (dep.nil? && !lock_source.nil?) || (!dep.nil? && !lock_source.nil? && !lock_source.can_lock?(dep))
344
+ gemfile_source_name = (dep && dep.source) || 'no specified source'
345
+ lockfile_source_name = lock_source || 'no specified source'
346
+ changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
347
+ end
348
+ end
349
+
350
+ msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
351
+ msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
352
+ msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
353
+ msg << "\n"
354
+
355
+ raise ProductionError, msg if added.any? || deleted.any? || changed.any?
356
+ end
357
+
358
+ def validate_ruby!
359
+ return unless ruby_version
360
+
361
+ if diff = ruby_version.diff(Carat.ruby_version)
362
+ problem, expected, actual = diff
363
+
364
+ msg = case problem
365
+ when :engine
366
+ "Your Ruby engine is #{actual}, but your Gemfile specified #{expected}"
367
+ when :version
368
+ "Your Ruby version is #{actual}, but your Gemfile specified #{expected}"
369
+ when :engine_version
370
+ "Your #{Carat.ruby_version.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}"
371
+ when :patchlevel
372
+ if !expected.is_a?(String)
373
+ "The Ruby patchlevel in your Gemfile must be a string"
374
+ else
375
+ "Your Ruby patchlevel is #{actual}, but your Gemfile specified #{expected}"
376
+ end
377
+ end
378
+
379
+ raise RubyVersionMismatch, msg
380
+ end
381
+ end
382
+
383
+ attr_reader :sources
384
+ private :sources
385
+
386
+ private
387
+
388
+ def nothing_changed?
389
+ !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes
390
+ end
391
+
392
+ def pretty_dep(dep, source = false)
393
+ msg = "#{dep.name}"
394
+ msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default
395
+ msg << " from the `#{dep.source}` source" if source && dep.source
396
+ msg
397
+ end
398
+
399
+ # Check if the specs of the given source changed
400
+ # according to the locked source. A block should be
401
+ # in order to specify how the locked version of
402
+ # the source should be found.
403
+ def specs_changed?(source, &block)
404
+ locked = @locked_sources.find(&block)
405
+
406
+ if locked
407
+ unlocking = @locked_specs.any? do |locked_spec|
408
+ locked_spec.source.class == locked.class && locked_spec.source != locked
409
+ end
410
+ end
411
+
412
+ !locked || unlocking || dependencies_for_source_changed?(locked) || source.specs != locked.specs
413
+ end
414
+
415
+ def dependencies_for_source_changed?(source)
416
+ deps_for_source = @dependencies.select { |s| s.source == source }
417
+ locked_deps_for_source = @locked_deps.select { |s| s.source == source }
418
+
419
+ deps_for_source != locked_deps_for_source
420
+ end
421
+
422
+ # Get all locals and override their matching sources.
423
+ # Return true if any of the locals changed (for example,
424
+ # they point to a new revision) or depend on new specs.
425
+ def converge_locals
426
+ locals = []
427
+
428
+ Carat.settings.local_overrides.map do |k,v|
429
+ spec = @dependencies.find { |s| s.name == k }
430
+ source = spec && spec.source
431
+ if source && source.respond_to?(:local_override!)
432
+ source.unlock! if @unlock[:gems].include?(spec.name)
433
+ locals << [ source, source.local_override!(v) ]
434
+ end
435
+ end
436
+
437
+ locals.any? do |source, changed|
438
+ changed || specs_changed?(source) { |o| source.class == o.class && source.uri == o.uri }
439
+ end
440
+ end
441
+
442
+ def converge_paths
443
+ sources.path_sources.any? do |source|
444
+ specs_changed?(source) do |ls|
445
+ ls.class == source.class && ls.path == source.path
446
+ end
447
+ end
448
+ end
449
+
450
+ def converge_sources
451
+ changes = false
452
+
453
+ # Get the Rubygems sources from the Gemfile.lock
454
+ locked_gem_sources = @locked_sources.select { |s| s.kind_of?(Source::Rubygems) }
455
+ # Get the Rubygems remotes from the Gemfile
456
+ actual_remotes = sources.rubygems_remotes
457
+
458
+ # If there is a Rubygems source in both
459
+ if !locked_gem_sources.empty? && !actual_remotes.empty?
460
+ locked_gem_sources.each do |locked_gem|
461
+ # Merge the remotes from the Gemfile into the Gemfile.lock
462
+ changes = changes | locked_gem.replace_remotes(actual_remotes)
463
+ end
464
+ end
465
+
466
+ # Replace the sources from the Gemfile with the sources from the Gemfile.lock,
467
+ # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent
468
+ # source in the Gemfile.lock, use the one from the Gemfile.
469
+ changes = changes | sources.replace_sources!(@locked_sources)
470
+
471
+ sources.all_sources.each do |source|
472
+ # If the source is unlockable and the current command allows an unlock of
473
+ # the source (for example, you are doing a `carat update <foo>` of a git-pinned
474
+ # gem), unlock it. For git sources, this means to unlock the revision, which
475
+ # will cause the `ref` used to be the most recent for the branch (or master) if
476
+ # an explicit `ref` is not used.
477
+ if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name)
478
+ source.unlock!
479
+ changes = true
480
+ end
481
+ end
482
+
483
+ changes
484
+ end
485
+
486
+ def converge_dependencies
487
+ (@dependencies + @locked_deps).each do |dep|
488
+ if dep.source
489
+ dep.source = sources.get(dep.source)
490
+ end
491
+ end
492
+ Set.new(@dependencies) != Set.new(@locked_deps)
493
+ end
494
+
495
+ # Remove elements from the locked specs that are expired. This will most
496
+ # commonly happen if the Gemfile has changed since the lockfile was last
497
+ # generated
498
+ def converge_locked_specs
499
+ deps = []
500
+
501
+ # Build a list of dependencies that are the same in the Gemfile
502
+ # and Gemfile.lock. If the Gemfile modified a dependency, but
503
+ # the gem in the Gemfile.lock still satisfies it, this is fine
504
+ # too.
505
+ locked_deps_hash = @locked_deps.inject({}) { |hsh, dep| hsh[dep] = dep; hsh }
506
+ @dependencies.each do |dep|
507
+ locked_dep = locked_deps_hash[dep]
508
+
509
+ if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
510
+ deps << dep
511
+ elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
512
+ @locked_specs.each do |s|
513
+ @unlock[:gems] << s.name if s.source == dep.source
514
+ end
515
+
516
+ dep.source.unlock! if dep.source.respond_to?(:unlock!)
517
+ dep.source.specs.each { |s| @unlock[:gems] << s.name }
518
+ end
519
+ end
520
+
521
+ converged = []
522
+ @locked_specs.each do |s|
523
+ # Replace the locked dependency's source with the equivalent source from the Gemfile
524
+ dep = @dependencies.find { |d| s.satisfies?(d) }
525
+ s.source = (dep && dep.source) || sources.get(s.source)
526
+
527
+ # Don't add a spec to the list if its source is expired. For example,
528
+ # if you change a Git gem to Rubygems.
529
+ next if s.source.nil? || @unlock[:sources].include?(s.name)
530
+ # If the spec is from a path source and it doesn't exist anymore
531
+ # then we just unlock it.
532
+
533
+ # Path sources have special logic
534
+ if s.source.instance_of?(Source::Path)
535
+ other = s.source.specs[s].first
536
+
537
+ # If the spec is no longer in the path source, unlock it. This
538
+ # commonly happens if the version changed in the gemspec
539
+ next unless other
540
+
541
+ deps2 = other.dependencies.select { |d| d.type != :development }
542
+ # If the dependencies of the path source have changed, unlock it
543
+ next unless s.dependencies.sort == deps2.sort
544
+ end
545
+
546
+ converged << s
547
+ end
548
+
549
+ resolve = SpecSet.new(converged)
550
+ resolve = resolve.for(expand_dependencies(deps, true), @unlock[:gems])
551
+ diff = @locked_specs.to_a - resolve.to_a
552
+
553
+ # Now, we unlock any sources that do not have anymore gems pinned to it
554
+ sources.all_sources.each do |source|
555
+ next unless source.respond_to?(:unlock!)
556
+
557
+ unless resolve.any? { |s| s.source == source }
558
+ source.unlock! if !diff.empty? && diff.any? { |s| s.source == source }
559
+ end
560
+ end
561
+
562
+ resolve
563
+ end
564
+
565
+ def in_locked_deps?(dep, locked_dep)
566
+ # Because the lockfile can't link a dep to a specific remote, we need to
567
+ # treat sources as equivalent anytime the locked dep has all the remotes
568
+ # that the Gemfile dep does.
569
+ locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
570
+ end
571
+
572
+ def satisfies_locked_spec?(dep)
573
+ @locked_specs.any? { |s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
574
+ end
575
+
576
+ def expanded_dependencies
577
+ @expanded_dependencies ||= expand_dependencies(dependencies, @remote)
578
+ end
579
+
580
+ def expand_dependencies(dependencies, remote = false)
581
+ deps = []
582
+ dependencies.each do |dep|
583
+ dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
584
+ next unless remote || dep.current_platform?
585
+ dep.gem_platforms(@platforms).each do |p|
586
+ deps << DepProxy.new(dep, p) if remote || p == generic(Gem::Platform.local)
587
+ end
588
+ end
589
+ deps
590
+ end
591
+
592
+ def requested_dependencies
593
+ groups = self.groups - Carat.settings.without
594
+ groups.map! { |g| g.to_sym }
595
+ dependencies.reject { |d| !d.should_include? || (d.groups & groups).empty? }
596
+ end
597
+
598
+ def source_requirements
599
+ # Load all specs from remote sources
600
+ index
601
+
602
+ # Record the specs available in each gem's source, so that those
603
+ # specs will be available later when the resolver knows where to
604
+ # look for that gemspec (or its dependencies)
605
+ source_requirements = {}
606
+ dependencies.each do |dep|
607
+ next unless dep.source
608
+ source_requirements[dep.name] = dep.source.specs
609
+ end
610
+ source_requirements
611
+ end
612
+
613
+ def pinned_spec_names(specs)
614
+ names = []
615
+ specs.each do |s|
616
+ # TODO when two sources without blocks is an error, we can change
617
+ # this check to !s.source.is_a?(Source::LocalRubygems). For now,
618
+ # we need to ask every Rubygems for every gem name.
619
+ if s.source.is_a?(Source::Git) || s.source.is_a?(Source::Path)
620
+ names << s.name
621
+ end
622
+ end
623
+ names.uniq!
624
+ names
625
+ end
626
+
627
+ end
628
+ end