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,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