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,162 @@
1
+ module Carat
2
+ class Source
3
+ class Git < Path
4
+
5
+ class GitNotInstalledError < GitError
6
+ def initialize
7
+ msg = "You need to install git to be able to use gems from git repositories. "
8
+ msg << "For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git"
9
+ super msg
10
+ end
11
+ end
12
+
13
+ class GitNotAllowedError < GitError
14
+ def initialize(command)
15
+ msg = "Carat is trying to run a `git #{command}` at runtime. You probably need to run `carat install`. However, "
16
+ msg << "this error message could probably be more useful. Please submit a ticket at http://github.com/caratrb/carat/issues "
17
+ msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
18
+ super msg
19
+ end
20
+ end
21
+
22
+ class GitCommandError < GitError
23
+ def initialize(command, path = nil)
24
+ msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed."
25
+ msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path && path.exist?
26
+ super msg
27
+ end
28
+ end
29
+
30
+ # The GitProxy is responsible to interact with git repositories.
31
+ # All actions required by the Git source is encapsulated in this
32
+ # object.
33
+ class GitProxy
34
+ attr_accessor :path, :uri, :ref
35
+ attr_writer :revision
36
+
37
+ def initialize(path, uri, ref, revision = nil, git = nil)
38
+ @path = path
39
+ @uri = uri
40
+ @ref = ref
41
+ @revision = revision
42
+ @git = git
43
+ raise GitNotInstalledError.new if allow? && !Carat.git_present?
44
+ end
45
+
46
+ def revision
47
+ @revision ||= allowed_in_path { git("rev-parse #{ref}").strip }
48
+ end
49
+
50
+ def branch
51
+ @branch ||= allowed_in_path do
52
+ git("branch") =~ /^\* (.*)$/ && $1.strip
53
+ end
54
+ end
55
+
56
+ def contains?(commit)
57
+ allowed_in_path do
58
+ result = git_null("branch --contains #{commit}")
59
+ $? == 0 && result =~ /^\* (.*)$/
60
+ end
61
+ end
62
+
63
+ def version
64
+ git("--version").sub("git version", "").strip
65
+ end
66
+
67
+ def checkout
68
+ if path.exist?
69
+ return if has_revision_cached?
70
+ Carat.ui.confirm "Updating #{uri}"
71
+ in_path do
72
+ git_retry %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"|
73
+ end
74
+ else
75
+ Carat.ui.info "Fetching #{uri}"
76
+ FileUtils.mkdir_p(path.dirname)
77
+ git_retry %|clone #{uri_escaped} "#{path}" --bare --no-hardlinks --quiet|
78
+ end
79
+ end
80
+
81
+ def copy_to(destination, submodules=false)
82
+ unless File.exist?(destination.join(".git"))
83
+ FileUtils.mkdir_p(destination.dirname)
84
+ FileUtils.rm_rf(destination)
85
+ git_retry %|clone --no-checkout --quiet "#{path}" "#{destination}"|
86
+ File.chmod((0777 & ~File.umask), destination)
87
+ end
88
+
89
+ SharedHelpers.chdir(destination) do
90
+ git_retry %|fetch --force --quiet --tags "#{path}"|
91
+ git "reset --hard #{@revision}"
92
+
93
+ if submodules
94
+ git_retry "submodule update --init --recursive"
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # TODO: Do not rely on /dev/null.
102
+ # Given that open3 is not cross platform until Ruby 1.9.3,
103
+ # the best solution is to pipe to /dev/null if it exists.
104
+ # If it doesn't, everything will work fine, but the user
105
+ # will get the $stderr messages as well.
106
+ def git_null(command)
107
+ git("#{command} 2>#{Carat::NULL}", false)
108
+ end
109
+
110
+ def git_retry(command)
111
+ Carat::Retry.new("git #{command}", GitNotAllowedError).attempts do
112
+ git(command)
113
+ end
114
+ end
115
+
116
+ def git(command, check_errors=true)
117
+ raise GitNotAllowedError.new(command) unless allow?
118
+ out = SharedHelpers.with_clean_git_env { %x{git #{command}} }
119
+ raise GitCommandError.new(command, path) if check_errors && !$?.success?
120
+ out
121
+ end
122
+
123
+ def has_revision_cached?
124
+ return unless @revision
125
+ in_path { git("cat-file -e #{@revision}") }
126
+ true
127
+ rescue GitError
128
+ false
129
+ end
130
+
131
+ # Escape the URI for git commands
132
+ def uri_escaped
133
+ if Carat::WINDOWS
134
+ # Windows quoting requires double quotes only, with double quotes
135
+ # inside the string escaped by being doubled.
136
+ '"' + uri.gsub('"') {|s| '""'} + '"'
137
+ else
138
+ # Bash requires single quoted strings, with the single quotes escaped
139
+ # by ending the string, escaping the quote, and restarting the string.
140
+ "'" + uri.gsub("'") {|s| "'\\''"} + "'"
141
+ end
142
+ end
143
+
144
+ def allow?
145
+ @git ? @git.allow_git_ops? : true
146
+ end
147
+
148
+ def in_path(&blk)
149
+ checkout unless path.exist?
150
+ SharedHelpers.chdir(path, &blk)
151
+ end
152
+
153
+ def allowed_in_path
154
+ return in_path { yield } if allow?
155
+ raise GitError, "The git source #{uri} is not yet checked out. Please run `carat install` before trying to start your application"
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,226 @@
1
+ module Carat
2
+ class Source
3
+
4
+ class Path < Source
5
+ autoload :Installer, 'carat/source/path/installer'
6
+
7
+ attr_reader :path, :options
8
+ attr_writer :name
9
+ attr_accessor :version
10
+
11
+ DEFAULT_GLOB = "{,*,*/*}.gemspec"
12
+
13
+ def initialize(options)
14
+ @options = options
15
+ @glob = options["glob"] || DEFAULT_GLOB
16
+
17
+ @allow_cached = false
18
+ @allow_remote = false
19
+
20
+ if options["path"]
21
+ @path = Pathname.new(options["path"])
22
+ @path = expand(@path) unless @path.relative?
23
+ end
24
+
25
+ @name = options["name"]
26
+ @version = options["version"]
27
+
28
+ # Stores the original path. If at any point we move to the
29
+ # cached directory, we still have the original path to copy from.
30
+ @original_path = @path
31
+ end
32
+
33
+ def remote!
34
+ @allow_remote = true
35
+ end
36
+
37
+ def cached!
38
+ @allow_cached = true
39
+ end
40
+
41
+ def self.from_lock(options)
42
+ new(options.merge("path" => options.delete("remote")))
43
+ end
44
+
45
+ def to_lock
46
+ out = "PATH\n"
47
+ out << " remote: #{relative_path}\n"
48
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
49
+ out << " specs:\n"
50
+ end
51
+
52
+ def to_s
53
+ "source at #{@path}"
54
+ end
55
+
56
+ def hash
57
+ [self.class, expanded_path, version].hash
58
+ end
59
+
60
+ def eql?(o)
61
+ o.instance_of?(Path) &&
62
+ expanded_path == expand(o.path) &&
63
+ version == o.version
64
+ end
65
+
66
+ alias == eql?
67
+
68
+ def name
69
+ File.basename(expanded_path.to_s)
70
+ end
71
+
72
+ def install(spec)
73
+ generate_bin(spec, :disable_extensions)
74
+ ["Using #{version_message(spec)} from #{to_s}", nil]
75
+ end
76
+
77
+ def cache(spec, custom_path = nil)
78
+ app_cache_path = app_cache_path(custom_path)
79
+ return unless Carat.settings[:cache_all]
80
+ return if expand(@original_path).to_s.index(Carat.root.to_s) == 0
81
+
82
+ unless @original_path.exist?
83
+ raise GemNotFound, "Can't cache gem #{version_message(spec)} because #{to_s} is missing!"
84
+ end
85
+
86
+ FileUtils.rm_rf(app_cache_path)
87
+ FileUtils.cp_r("#{@original_path}/.", app_cache_path)
88
+ FileUtils.touch(app_cache_path.join(".caratcache"))
89
+ end
90
+
91
+ def local_specs(*)
92
+ @local_specs ||= load_spec_files
93
+ end
94
+
95
+ def specs
96
+ if has_app_cache?
97
+ @path = app_cache_path
98
+ @expanded_path = nil # Invalidate
99
+ end
100
+ local_specs
101
+ end
102
+
103
+ def app_cache_dirname
104
+ name
105
+ end
106
+
107
+ private
108
+
109
+ def expanded_path
110
+ @expanded_path ||= expand(path)
111
+ end
112
+
113
+ def expand(somepath)
114
+ somepath.expand_path(Carat.root)
115
+ rescue ArgumentError => e
116
+ Carat.ui.debug(e)
117
+ raise PathError, "There was an error while trying to use the path " \
118
+ "`#{somepath}`.\nThe error message was: #{e.message}."
119
+ end
120
+
121
+ def app_cache_path(custom_path = nil)
122
+ @app_cache_path ||= Carat.app_cache(custom_path).join(app_cache_dirname)
123
+ end
124
+
125
+ def has_app_cache?
126
+ SharedHelpers.in_bundle? && app_cache_path.exist?
127
+ end
128
+
129
+ def load_spec_files
130
+ index = Index.new
131
+
132
+ if File.directory?(expanded_path)
133
+ # We sort depth-first since `<<` will override the earlier-found specs
134
+ Dir["#{expanded_path}/#{@glob}"].sort_by { |p| -p.split(File::SEPARATOR).size }.each do |file|
135
+ spec = Carat.load_gemspec(file)
136
+ if spec
137
+ spec.loaded_from = file.to_s
138
+ spec.source = self
139
+ index << spec
140
+ end
141
+ end
142
+
143
+ if index.empty? && @name && @version
144
+ index << Gem::Specification.new do |s|
145
+ s.name = @name
146
+ s.source = self
147
+ s.version = Gem::Version.new(@version)
148
+ s.platform = Gem::Platform::RUBY
149
+ s.summary = "Fake gemspec for #{@name}"
150
+ s.relative_loaded_from = "#{@name}.gemspec"
151
+ s.authors = ["no one"]
152
+ if expanded_path.join("bin").exist?
153
+ executables = expanded_path.join("bin").children
154
+ executables.reject!{|p| File.directory?(p) }
155
+ s.executables = executables.map{|c| c.basename.to_s }
156
+ end
157
+ end
158
+ end
159
+ elsif File.exist?(expanded_path)
160
+ raise PathError, "The path `#{expanded_path}` is not a directory."
161
+ else
162
+ raise PathError, "The path `#{expanded_path}` does not exist."
163
+ end
164
+
165
+ index
166
+ end
167
+
168
+ def relative_path
169
+ if path.to_s.match(%r{^#{Regexp.escape Carat.root.to_s}})
170
+ return path.relative_path_from(Carat.root)
171
+ end
172
+ path
173
+ end
174
+
175
+ def generate_bin(spec, disable_extensions = false)
176
+ gem_dir = Pathname.new(spec.full_gem_path)
177
+
178
+ # Some gem authors put absolute paths in their gemspec
179
+ # and we have to save them from themselves
180
+ spec.files = spec.files.map do |p|
181
+ next if File.directory?(p)
182
+ begin
183
+ Pathname.new(p).relative_path_from(gem_dir).to_s
184
+ rescue ArgumentError
185
+ p
186
+ end
187
+ end.compact
188
+
189
+ SharedHelpers.chdir(gem_dir) do
190
+ installer = Path::Installer.new(spec, :env_shebang => false)
191
+ run_hooks(:pre_install, installer)
192
+ installer.build_extensions unless disable_extensions
193
+ run_hooks(:post_build, installer)
194
+ installer.generate_bin
195
+ run_hooks(:post_install, installer)
196
+ end
197
+ rescue Gem::InvalidSpecificationException => e
198
+ Carat.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \
199
+ "This prevents carat from installing bins or native extensions, but " \
200
+ "that may not affect its functionality."
201
+
202
+ if !spec.extensions.empty? && !spec.email.empty?
203
+ Carat.ui.warn "If you need to use this package without installing it from a gem " \
204
+ "repository, please contact #{spec.email} and ask them " \
205
+ "to modify their .gemspec so it can work with `gem build`."
206
+ end
207
+
208
+ Carat.ui.warn "The validation message from Rubygems was:\n #{e.message}"
209
+ end
210
+
211
+ def run_hooks(type, installer)
212
+ hooks_meth = "#{type}_hooks"
213
+ return unless Gem.respond_to?(hooks_meth)
214
+ Gem.send(hooks_meth).each do |hook|
215
+ result = hook.call(installer)
216
+ if result == false
217
+ location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
218
+ message = "#{type} hook#{location} failed for #{installer.spec.full_name}"
219
+ raise InstallHookError, message
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ end
226
+ end
@@ -0,0 +1,43 @@
1
+ module Carat
2
+ class Source
3
+ class Path
4
+
5
+ class Installer < Carat::GemInstaller
6
+ attr_reader :spec
7
+
8
+ def initialize(spec, options = {})
9
+ @spec = spec
10
+ @gem_dir = Carat.rubygems.path(spec.full_gem_path)
11
+ @wrappers = options[:wrappers] || true
12
+ @env_shebang = options[:env_shebang] || true
13
+ @format_executable = options[:format_executable] || false
14
+ @build_args = options[:build_args] || Carat.rubygems.build_args
15
+ @gem_bin_dir = "#{Carat.rubygems.gem_dir}/bin"
16
+
17
+ if Carat.requires_sudo?
18
+ @tmp_dir = Carat.tmp(spec.full_name).to_s
19
+ @bin_dir = "#{@tmp_dir}/bin"
20
+ else
21
+ @bin_dir = @gem_bin_dir
22
+ end
23
+ end
24
+
25
+ def generate_bin
26
+ return if spec.executables.nil? || spec.executables.empty?
27
+
28
+ super
29
+
30
+ if Carat.requires_sudo?
31
+ Carat.mkdir_p @gem_bin_dir
32
+ spec.executables.each do |exe|
33
+ Carat.sudo "cp -R #{@bin_dir}/#{exe} #{@gem_bin_dir}"
34
+ end
35
+ end
36
+ ensure
37
+ Carat.rm_rf(@tmp_dir) if Carat.requires_sudo?
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,381 @@
1
+ require 'uri'
2
+ require 'rubygems/user_interaction'
3
+ require 'rubygems/spec_fetcher'
4
+
5
+ module Carat
6
+ class Source
7
+ class Rubygems < Source
8
+ # Use the API when installing less than X gems
9
+ API_REQUEST_LIMIT = 500
10
+ # Ask for X gems per API request
11
+ API_REQUEST_SIZE = 50
12
+
13
+ attr_reader :remotes, :caches
14
+
15
+ def initialize(options = {})
16
+ @options = options
17
+ @remotes = []
18
+ @dependency_names = []
19
+ @allow_remote = false
20
+ @allow_cached = false
21
+ @caches = [Carat.app_cache, *Carat.rubygems.gem_cache]
22
+
23
+ Array(options["remotes"] || []).reverse_each{|r| add_remote(r) }
24
+ end
25
+
26
+ def remote!
27
+ @allow_remote = true
28
+ end
29
+
30
+ def cached!
31
+ @allow_cached = true
32
+ end
33
+
34
+ def hash
35
+ @remotes.hash
36
+ end
37
+
38
+ def eql?(o)
39
+ o.is_a?(Rubygems) && o.credless_remotes == credless_remotes
40
+ end
41
+
42
+ alias == eql?
43
+
44
+ def include?(o)
45
+ o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty?
46
+ end
47
+
48
+ def can_lock?(spec)
49
+ spec.source.is_a?(Rubygems)
50
+ end
51
+
52
+ def options
53
+ { "remotes" => @remotes.map { |r| r.to_s } }
54
+ end
55
+
56
+ def self.from_lock(options)
57
+ new(options)
58
+ end
59
+
60
+ def to_lock
61
+ out = "GEM\n"
62
+ remotes.reverse_each do |remote|
63
+ out << " remote: #{suppress_configured_credentials remote}\n"
64
+ end
65
+ out << " specs:\n"
66
+ end
67
+
68
+ def to_s
69
+ remote_names = self.remotes.map { |r| r.to_s }.join(', ')
70
+ "rubygems repository #{remote_names}"
71
+ end
72
+ alias_method :name, :to_s
73
+
74
+ def specs
75
+ @specs ||= begin
76
+ # remote_specs usually generates a way larger Index than the other
77
+ # sources, and large_idx.use small_idx is way faster than
78
+ # small_idx.use large_idx.
79
+ idx = @allow_remote ? remote_specs.dup : Index.new
80
+ idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
81
+ idx.use(installed_specs, :override_dupes)
82
+ idx
83
+ end
84
+ end
85
+
86
+ def install(spec)
87
+ return ["Using #{version_message(spec)}", nil] if installed_specs[spec].any?
88
+
89
+ # Download the gem to get the spec, because some specs that are returned
90
+ # by rubygems.org are broken and wrong.
91
+ if spec.source_uri
92
+ # Check for this spec from other sources
93
+ uris = [spec.source_uri.without_credentials]
94
+ uris += source_uris_for_spec(spec)
95
+ uris.uniq!
96
+ Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1
97
+
98
+ s = Carat.rubygems.spec_from_gem(fetch_gem(spec), Carat.settings["trust-policy"])
99
+ spec.__swap__(s)
100
+ end
101
+
102
+ unless Carat.settings[:no_install]
103
+ path = cached_gem(spec)
104
+ if Carat.requires_sudo?
105
+ install_path = Carat.tmp(spec.full_name)
106
+ bin_path = install_path.join("bin")
107
+ else
108
+ install_path = Carat.rubygems.gem_dir
109
+ bin_path = Carat.system_bindir
110
+ end
111
+
112
+ installed_spec = nil
113
+ Carat.rubygems.preserve_paths do
114
+ installed_spec = Carat::GemInstaller.new(path,
115
+ :install_dir => install_path.to_s,
116
+ :bin_dir => bin_path.to_s,
117
+ :ignore_dependencies => true,
118
+ :wrappers => true,
119
+ :env_shebang => true
120
+ ).install
121
+ end
122
+
123
+ # SUDO HAX
124
+ if Carat.requires_sudo?
125
+ Carat.rubygems.repository_subdirectories.each do |name|
126
+ src = File.join(install_path, name, "*")
127
+ dst = File.join(Carat.rubygems.gem_dir, name)
128
+ if name == "extensions" && Dir.glob(src).any?
129
+ src = File.join(src, "*/*")
130
+ ext_src = Dir.glob(src).first
131
+ ext_src.gsub!(src[0..-6], '')
132
+ dst = File.dirname(File.join(dst, ext_src))
133
+ end
134
+ Carat.mkdir_p dst
135
+ Carat.sudo "cp -R #{src} #{dst}" if Dir[src].any?
136
+ end
137
+
138
+ spec.executables.each do |exe|
139
+ Carat.mkdir_p Carat.system_bindir
140
+ Carat.sudo "cp -R #{install_path}/bin/#{exe} #{Carat.system_bindir}/"
141
+ end
142
+ end
143
+ installed_spec.loaded_from = loaded_from(spec)
144
+ end
145
+ spec.loaded_from = loaded_from(spec)
146
+ ["Installing #{version_message(spec)}", spec.post_install_message]
147
+ ensure
148
+ Carat.rm_rf(install_path) if Carat.requires_sudo?
149
+ end
150
+
151
+ def cache(spec, custom_path = nil)
152
+ if builtin_gem?(spec)
153
+ cached_path = cached_built_in_gem(spec)
154
+ else
155
+ cached_path = cached_gem(spec)
156
+ end
157
+ raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
158
+ return if File.dirname(cached_path) == Carat.app_cache.to_s
159
+ Carat.ui.info " * #{File.basename(cached_path)}"
160
+ FileUtils.cp(cached_path, Carat.app_cache(custom_path))
161
+ rescue Errno::EACCES => e
162
+ Carat.ui.debug(e)
163
+ raise InstallError, e.message
164
+ end
165
+
166
+ def cached_built_in_gem(spec)
167
+ cached_path = cached_path(spec)
168
+ if cached_path.nil?
169
+ remote_spec = remote_specs.search(spec).first
170
+ cached_path = fetch_gem(remote_spec)
171
+ end
172
+ cached_path
173
+ end
174
+
175
+ def add_remote(source)
176
+ uri = normalize_uri(source)
177
+ @remotes.unshift(uri) unless @remotes.include?(uri)
178
+ end
179
+
180
+ def replace_remotes(other_remotes)
181
+ return false if other_remotes == @remotes
182
+
183
+ @remotes = []
184
+ other_remotes.reverse_each do |r|
185
+ add_remote r.to_s
186
+ end
187
+ end
188
+
189
+ def unmet_deps
190
+ if @allow_remote && api_fetchers.any?
191
+ remote_specs.unmet_dependency_names
192
+ else
193
+ []
194
+ end
195
+ end
196
+
197
+ def fetchers
198
+ @fetchers ||= remotes.map do |uri|
199
+ Carat::Fetcher.new(uri)
200
+ end
201
+ end
202
+
203
+ protected
204
+
205
+ def credless_remotes
206
+ remotes.map(&method(:suppress_configured_credentials))
207
+ end
208
+
209
+ private
210
+
211
+ def source_uris_for_spec(spec)
212
+ specs.search_all(spec.name).inject([]) do |uris, s|
213
+ uris << s.source_uri.without_credentials if s.source_uri
214
+ uris
215
+ end
216
+ end
217
+
218
+ def loaded_from(spec)
219
+ "#{Carat.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
220
+ end
221
+
222
+ def cached_gem(spec)
223
+ cached_gem = cached_path(spec)
224
+ unless cached_gem
225
+ raise Carat::GemNotFound, "Could not find #{spec.file_name} for installation"
226
+ end
227
+ cached_gem
228
+ end
229
+
230
+ def cached_path(spec)
231
+ possibilities = @caches.map { |p| "#{p}/#{spec.file_name}" }
232
+ possibilities.find { |p| File.exist?(p) }
233
+ end
234
+
235
+ def normalize_uri(uri)
236
+ uri = uri.to_s
237
+ uri = "#{uri}/" unless uri =~ %r'/$'
238
+ uri = URI(uri)
239
+ raise ArgumentError, "The source must be an absolute URI" unless uri.absolute?
240
+ uri
241
+ end
242
+
243
+ def suppress_configured_credentials(remote)
244
+ remote_nouser = remote.dup.tap { |uri| uri.user = uri.password = nil }.to_s
245
+ if remote.userinfo && remote.userinfo == Carat.settings[remote_nouser]
246
+ remote_nouser
247
+ else
248
+ remote
249
+ end
250
+ end
251
+
252
+ def installed_specs
253
+ @installed_specs ||= begin
254
+ idx = Index.new
255
+ have_carat = false
256
+ Carat.rubygems.all_specs.reverse.each do |spec|
257
+ next if spec.name == 'carat' && spec.version.to_s != VERSION
258
+ have_carat = true if spec.name == 'carat'
259
+ spec.source = self
260
+ idx << spec
261
+ end
262
+
263
+ # Always have carat locally
264
+ unless have_carat
265
+ # We're running carat directly from the source
266
+ # so, let's create a fake gemspec for it (it's a path)
267
+ # gemspec
268
+ carat = Gem::Specification.new do |s|
269
+ s.name = 'carat'
270
+ s.version = VERSION
271
+ s.platform = Gem::Platform::RUBY
272
+ s.source = self
273
+ s.authors = ["carat team"]
274
+ s.loaded_from = File.expand_path("..", __FILE__)
275
+ end
276
+ idx << carat
277
+ end
278
+ idx
279
+ end
280
+ end
281
+
282
+ def cached_specs
283
+ @cached_specs ||= begin
284
+ idx = installed_specs.dup
285
+
286
+ path = Carat.app_cache
287
+ Dir["#{path}/*.gem"].each do |gemfile|
288
+ next if gemfile =~ /^carat\-[\d\.]+?\.gem/
289
+ s ||= Carat.rubygems.spec_from_gem(gemfile)
290
+ s.source = self
291
+ idx << s
292
+ end
293
+ end
294
+
295
+ idx
296
+ end
297
+
298
+ def api_fetchers
299
+ fetchers.select{|f| f.use_api }
300
+ end
301
+
302
+ def remote_specs
303
+ @remote_specs ||= Index.build do |idx|
304
+ index_fetchers = fetchers - api_fetchers
305
+
306
+ # gather lists from non-api sites
307
+ index_fetchers.each do |f|
308
+ Carat.ui.info "Fetching source index from #{f.uri}"
309
+ idx.use f.specs(nil, self)
310
+ end
311
+
312
+ # because ensuring we have all the gems we need involves downloading
313
+ # the gemspecs of those gems, if the non-api sites contain more than
314
+ # about 100 gems, we just treat all sites as non-api for speed.
315
+ allow_api = idx.size < API_REQUEST_LIMIT && dependency_names.size < API_REQUEST_LIMIT
316
+ Carat.ui.debug "Need to query more than #{API_REQUEST_LIMIT} gems." \
317
+ " Downloading full index instead..." unless allow_api
318
+
319
+ if allow_api
320
+ api_fetchers.each do |f|
321
+ Carat.ui.info "Fetching gem metadata from #{f.uri}", Carat.ui.debug?
322
+ idx.use f.specs(dependency_names, self)
323
+ Carat.ui.info "" if !Carat.ui.debug? # new line now that the dots are over
324
+ end
325
+
326
+ # Suppose the gem Foo depends on the gem Bar. Foo exists in Source A. Bar has some versions that exist in both
327
+ # sources A and B. At this point, the API request will have found all the versions of Bar in source A,
328
+ # but will not have found any versions of Bar from source B, which is a problem if the requested version
329
+ # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
330
+ # each spec we found, we add all possible versions from all sources to the index.
331
+ begin
332
+ idxcount = idx.size
333
+ api_fetchers.each do |f|
334
+ Carat.ui.info "Fetching version metadata from #{f.uri}", Carat.ui.debug?
335
+ idx.use f.specs(idx.dependency_names, self), true
336
+ Carat.ui.info "" if !Carat.ui.debug? # new line now that the dots are over
337
+ end
338
+ end until idxcount == idx.size
339
+
340
+ if api_fetchers.any? && api_fetchers.all?{|f| f.use_api }
341
+ # it's possible that gems from one source depend on gems from some
342
+ # other source, so now we download gemspecs and iterate over those
343
+ # dependencies, looking for gems we don't have info on yet.
344
+ unmet = idx.unmet_dependency_names
345
+
346
+ # if there are any cross-site gems we missed, get them now
347
+ api_fetchers.each do |f|
348
+ Carat.ui.info "Fetching dependency metadata from #{f.uri}", Carat.ui.debug?
349
+ idx.use f.specs(unmet, self)
350
+ Carat.ui.info "" if !Carat.ui.debug? # new line now that the dots are over
351
+ end if unmet.any?
352
+ else
353
+ allow_api = false
354
+ end
355
+ end
356
+
357
+ if !allow_api
358
+ api_fetchers.each do |f|
359
+ Carat.ui.info "Fetching source index from #{f.uri}"
360
+ idx.use f.specs(nil, self)
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ def fetch_gem(spec)
367
+ return false unless spec.source_uri
368
+ Fetcher.download_gem_from_uri(spec, spec.source_uri.original_uri)
369
+ end
370
+
371
+ def builtin_gem?(spec)
372
+ # Ruby 2.1, where all included gems have this summary
373
+ return true if spec.summary =~ /is bundled with Ruby/
374
+
375
+ # Ruby 2.0, where gemspecs are stored in specifications/default/
376
+ spec.loaded_from && spec.loaded_from.include?("specifications/default/")
377
+ end
378
+
379
+ end
380
+ end
381
+ end