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