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