| 1 |
#!/usr/bin/env ruby |
| 2 |
require 'rubygems' |
| 3 |
require 'thor' |
| 4 |
require 'fileutils' |
| 5 |
require 'yaml' |
| 6 |
|
| 7 |
# Important - don't change this line or its position |
| 8 |
MERB_THOR_VERSION = '0.2.1' |
| 9 |
|
| 10 |
############################################################################## |
| 11 |
|
| 12 |
module ColorfulMessages |
| 13 |
|
| 14 |
# red |
| 15 |
def error(*messages) |
| 16 |
puts messages.map { |msg| "\033[1;31m#{msg}\033[0m" } |
| 17 |
end |
| 18 |
|
| 19 |
# yellow |
| 20 |
def warning(*messages) |
| 21 |
puts messages.map { |msg| "\033[1;33m#{msg}\033[0m" } |
| 22 |
end |
| 23 |
|
| 24 |
# green |
| 25 |
def success(*messages) |
| 26 |
puts messages.map { |msg| "\033[1;32m#{msg}\033[0m" } |
| 27 |
end |
| 28 |
|
| 29 |
alias_method :message, :success |
| 30 |
|
| 31 |
# magenta |
| 32 |
def note(*messages) |
| 33 |
puts messages.map { |msg| "\033[1;35m#{msg}\033[0m" } |
| 34 |
end |
| 35 |
|
| 36 |
# blue |
| 37 |
def info(*messages) |
| 38 |
puts messages.map { |msg| "\033[1;34m#{msg}\033[0m" } |
| 39 |
end |
| 40 |
|
| 41 |
end |
| 42 |
|
| 43 |
############################################################################## |
| 44 |
|
| 45 |
require 'rubygems/dependency_installer' |
| 46 |
require 'rubygems/uninstaller' |
| 47 |
require 'rubygems/dependency' |
| 48 |
|
| 49 |
module GemManagement |
| 50 |
|
| 51 |
include ColorfulMessages |
| 52 |
|
| 53 |
# Install a gem - looks remotely and local gem cache; |
| 54 |
# won't process rdoc or ri options. |
| 55 |
def install_gem(gem, options = {}) |
| 56 |
refresh = options.delete(:refresh) || [] |
| 57 |
from_cache = (options.key?(:cache) && options.delete(:cache)) |
| 58 |
if from_cache |
| 59 |
install_gem_from_cache(gem, options) |
| 60 |
else |
| 61 |
version = options.delete(:version) |
| 62 |
Gem.configuration.update_sources = false |
| 63 |
|
| 64 |
# Limit source index to install dir |
| 65 |
update_source_index(options[:install_dir]) if options[:install_dir] |
| 66 |
|
| 67 |
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false)) |
| 68 |
|
| 69 |
# Force-refresh certain gems by excluding them from the current index |
| 70 |
if !options[:ignore_dependencies] && refresh.respond_to?(:include?) && !refresh.empty? |
| 71 |
source_index = installer.instance_variable_get(:@source_index) |
| 72 |
source_index.gems.each do |name, spec| |
| 73 |
source_index.gems.delete(name) if refresh.include?(spec.name) |
| 74 |
end |
| 75 |
end |
| 76 |
|
| 77 |
exception = nil |
| 78 |
begin |
| 79 |
installer.install gem, version |
| 80 |
rescue Gem::InstallError => e |
| 81 |
exception = e |
| 82 |
rescue Gem::GemNotFoundException => e |
| 83 |
if from_cache && gem_file = find_gem_in_cache(gem, version) |
| 84 |
puts "Located #{gem} in gem cache..." |
| 85 |
installer.install gem_file |
| 86 |
else |
| 87 |
exception = e |
| 88 |
end |
| 89 |
rescue => e |
| 90 |
exception = e |
| 91 |
end |
| 92 |
if installer.installed_gems.empty? && exception |
| 93 |
error "Failed to install gem '#{gem} (#{version || 'any version'})' (#{exception.message})" |
| 94 |
end |
| 95 |
ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options) |
| 96 |
installer.installed_gems.each do |spec| |
| 97 |
success "Successfully installed #{spec.full_name}" |
| 98 |
end |
| 99 |
return !installer.installed_gems.empty? |
| 100 |
end |
| 101 |
end |
| 102 |
|
| 103 |
# Install a gem - looks in the system's gem cache instead of remotely; |
| 104 |
# won't process rdoc or ri options. |
| 105 |
def install_gem_from_cache(gem, options = {}) |
| 106 |
version = options.delete(:version) |
| 107 |
Gem.configuration.update_sources = false |
| 108 |
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false)) |
| 109 |
exception = nil |
| 110 |
begin |
| 111 |
if gem_file = find_gem_in_cache(gem, version) |
| 112 |
puts "Located #{gem} in gem cache..." |
| 113 |
installer.install gem_file |
| 114 |
else |
| 115 |
raise Gem::InstallError, "Unknown gem #{gem}" |
| 116 |
end |
| 117 |
rescue Gem::InstallError => e |
| 118 |
exception = e |
| 119 |
end |
| 120 |
if installer.installed_gems.empty? && exception |
| 121 |
error "Failed to install gem '#{gem}' (#{e.message})" |
| 122 |
end |
| 123 |
ensure_bin_wrapper_for_installed_gems(installer.installed_gems, options) |
| 124 |
installer.installed_gems.each do |spec| |
| 125 |
success "Successfully installed #{spec.full_name}" |
| 126 |
end |
| 127 |
end |
| 128 |
|
| 129 |
# Install a gem from source - builds and packages it first then installs. |
| 130 |
# |
| 131 |
# Examples: |
| 132 |
# install_gem_from_source(source_dir, :install_dir => ...) |
| 133 |
# install_gem_from_source(source_dir, gem_name) |
| 134 |
# install_gem_from_source(source_dir, :skip => [...]) |
| 135 |
def install_gem_from_source(source_dir, *args) |
| 136 |
installed_gems = [] |
| 137 |
opts = args.last.is_a?(Hash) ? args.pop : {} |
| 138 |
Dir.chdir(source_dir) do |
| 139 |
gem_name = args[0] || File.basename(source_dir) |
| 140 |
gem_pkg_dir = File.join(source_dir, 'pkg') |
| 141 |
gem_pkg_glob = File.join(gem_pkg_dir, "#{gem_name}-*.gem") |
| 142 |
skip_gems = opts.delete(:skip) || [] |
| 143 |
|
| 144 |
# Cleanup what's already there |
| 145 |
clobber(source_dir) |
| 146 |
FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir) |
| 147 |
|
| 148 |
# Recursively process all gem packages within the source dir |
| 149 |
skip_gems << gem_name |
| 150 |
packages = package_all(source_dir, skip_gems) |
| 151 |
|
| 152 |
if packages.length == 1 |
| 153 |
# The are no subpackages for the main package |
| 154 |
refresh = [gem_name] |
| 155 |
else |
| 156 |
# Gather all packages into the top-level pkg directory |
| 157 |
packages.each do |pkg| |
| 158 |
FileUtils.copy_entry(pkg, File.join(gem_pkg_dir, File.basename(pkg))) |
| 159 |
end |
| 160 |
|
| 161 |
# Finally package the main gem - without clobbering the already copied pkgs |
| 162 |
package(source_dir, false) |
| 163 |
|
| 164 |
# Gather subgems to refresh during installation of the main gem |
| 165 |
refresh = packages.map do |pkg| |
| 166 |
File.basename(pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1] rescue nil |
| 167 |
end.compact |
| 168 |
|
| 169 |
# Install subgems explicitly even if ignore_dependencies is set |
| 170 |
if opts[:ignore_dependencies] |
| 171 |
refresh.each do |name| |
| 172 |
gem_pkg = Dir[File.join(gem_pkg_dir, "#{name}-*.gem")][0] |
| 173 |
install_pkg(gem_pkg, opts) |
| 174 |
end |
| 175 |
end |
| 176 |
end |
| 177 |
|
| 178 |
ensure_bin_wrapper_for(opts[:install_dir], opts[:bin_dir], *installed_gems) |
| 179 |
|
| 180 |
# Finally install the main gem |
| 181 |
if install_pkg(Dir[gem_pkg_glob][0], opts.merge(:refresh => refresh)) |
| 182 |
installed_gems = refresh |
| 183 |
else |
| 184 |
installed_gems = [] |
| 185 |
end |
| 186 |
end |
| 187 |
installed_gems |
| 188 |
end |
| 189 |
|
| 190 |
def install_pkg(gem_pkg, opts = {}) |
| 191 |
if (gem_pkg && File.exists?(gem_pkg)) |
| 192 |
# Needs to be executed from the directory that contains all packages |
| 193 |
Dir.chdir(File.dirname(gem_pkg)) { install_gem(gem_pkg, opts) } |
| 194 |
else |
| 195 |
false |
| 196 |
end |
| 197 |
end |
| 198 |
|
| 199 |
# Uninstall a gem. |
| 200 |
def uninstall_gem(gem, options = {}) |
| 201 |
if options[:version] && !options[:version].is_a?(Gem::Requirement) |
| 202 |
options[:version] = Gem::Requirement.new ["= #{options[:version]}"] |
| 203 |
end |
| 204 |
update_source_index(options[:install_dir]) if options[:install_dir] |
| 205 |
Gem::Uninstaller.new(gem, options).uninstall rescue nil |
| 206 |
end |
| 207 |
|
| 208 |
def clobber(source_dir) |
| 209 |
Dir.chdir(source_dir) do |
| 210 |
system "#{Gem.ruby} -S rake -s clobber" unless File.exists?('Thorfile') |
| 211 |
end |
| 212 |
end |
| 213 |
|
| 214 |
def package(source_dir, clobber = true) |
| 215 |
Dir.chdir(source_dir) do |
| 216 |
if File.exists?('Thorfile') |
| 217 |
thor ":package" |
| 218 |
elsif File.exists?('Rakefile') |
| 219 |
rake "clobber" if clobber |
| 220 |
rake "package" |
| 221 |
end |
| 222 |
end |
| 223 |
Dir[File.join(source_dir, 'pkg/*.gem')] |
| 224 |
end |
| 225 |
|
| 226 |
def package_all(source_dir, skip = [], packages = []) |
| 227 |
if Dir[File.join(source_dir, '{Rakefile,Thorfile}')][0] |
| 228 |
name = File.basename(source_dir) |
| 229 |
Dir[File.join(source_dir, '*', '{Rakefile,Thorfile}')].each do |taskfile| |
| 230 |
package_all(File.dirname(taskfile), skip, packages) |
| 231 |
end |
| 232 |
packages.push(*package(source_dir)) unless skip.include?(name) |
| 233 |
end |
| 234 |
packages.uniq |
| 235 |
end |
| 236 |
|
| 237 |
def rake(cmd) |
| 238 |
cmd << " >/dev/null" if $SILENT && !Gem.win_platform? |
| 239 |
system "#{Gem.ruby} -S #{which('rake')} -s #{cmd} >/dev/null" |
| 240 |
end |
| 241 |
|
| 242 |
def thor(cmd) |
| 243 |
cmd << " >/dev/null" if $SILENT && !Gem.win_platform? |
| 244 |
system "#{Gem.ruby} -S #{which('thor')} #{cmd}" |
| 245 |
end |
| 246 |
|
| 247 |
# Use the local bin/* executables if available. |
| 248 |
def which(executable) |
| 249 |
if File.executable?(exec = File.join(Dir.pwd, 'bin', executable)) |
| 250 |
exec |
| 251 |
else |
| 252 |
executable |
| 253 |
end |
| 254 |
end |
| 255 |
|
| 256 |
# Partition gems into system, local and missing gems |
| 257 |
def partition_dependencies(dependencies, gem_dir) |
| 258 |
system_specs, local_specs, missing_deps = [], [], [] |
| 259 |
if gem_dir && File.directory?(gem_dir) |
| 260 |
gem_dir = File.expand_path(gem_dir) |
| 261 |
::Gem.clear_paths; ::Gem.path.unshift(gem_dir) |
| 262 |
::Gem.source_index.refresh! |
| 263 |
dependencies.each do |dep| |
| 264 |
gemspecs = ::Gem.source_index.search(dep) |
| 265 |
local = gemspecs.reverse.find { |s| s.loaded_from.index(gem_dir) == 0 } |
| 266 |
if local |
| 267 |
local_specs << local |
| 268 |
elsif gemspecs.last |
| 269 |
system_specs << gemspecs.last |
| 270 |
else |
| 271 |
missing_deps << dep |
| 272 |
end |
| 273 |
end |
| 274 |
::Gem.clear_paths |
| 275 |
else |
| 276 |
dependencies.each do |dep| |
| 277 |
gemspecs = ::Gem.source_index.search(dep) |
| 278 |
if gemspecs.last |
| 279 |
system_specs << gemspecs.last |
| 280 |
else |
| 281 |
missing_deps << dep |
| 282 |
end |
| 283 |
end |
| 284 |
end |
| 285 |
[system_specs, local_specs, missing_deps] |
| 286 |
end |
| 287 |
|
| 288 |
# Create a modified executable wrapper in the specified bin directory. |
| 289 |
def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems) |
| 290 |
options = gems.last.is_a?(Hash) ? gems.last : {} |
| 291 |
options[:no_minigems] ||= [] |
| 292 |
if bin_dir && File.directory?(bin_dir) |
| 293 |
gems.each do |gem| |
| 294 |
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last |
| 295 |
spec = Gem::Specification.load(gemspec_path) |
| 296 |
enable_minigems = !options[:no_minigems].include?(spec.name) |
| 297 |
spec.executables.each do |exec| |
| 298 |
executable = File.join(bin_dir, exec) |
| 299 |
message "Writing executable wrapper #{executable}" |
| 300 |
File.open(executable, 'w', 0755) do |f| |
| 301 |
f.write(executable_wrapper(spec, exec, enable_minigems)) |
| 302 |
end |
| 303 |
end |
| 304 |
end |
| 305 |
end |
| 306 |
end |
| 307 |
end |
| 308 |
|
| 309 |
def ensure_bin_wrapper_for_installed_gems(gemspecs, options) |
| 310 |
if options[:install_dir] && options[:bin_dir] |
| 311 |
gems = gemspecs.map { |spec| spec.name } |
| 312 |
ensure_bin_wrapper_for(options[:install_dir], options[:bin_dir], *gems) |
| 313 |
end |
| 314 |
end |
| 315 |
|
| 316 |
private |
| 317 |
|
| 318 |
def executable_wrapper(spec, bin_file_name, minigems = true) |
| 319 |
requirements = ['minigems', 'rubygems'] |
| 320 |
requirements.reverse! unless minigems |
| 321 |
try_req, then_req = requirements |
| 322 |
<<-TEXT |
| 323 |
#!/usr/bin/env ruby |
| 324 |
# |
| 325 |
# This file was generated by Merb's GemManagement |
| 326 |
# |
| 327 |
# The application '#{spec.name}' is installed as part of a gem, and |
| 328 |
# this file is here to facilitate running it. |
| 329 |
|
| 330 |
begin |
| 331 |
require '#{try_req}' |
| 332 |
rescue LoadError |
| 333 |
require '#{then_req}' |
| 334 |
end |
| 335 |
|
| 336 |
# use gems dir if ../gems exists - eg. only for ./bin/#{bin_file_name} |
| 337 |
if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems')) |
| 338 |
$BUNDLE = true; Gem.clear_paths; Gem.path.replace([File.expand_path(gems_dir)]) |
| 339 |
ENV["PATH"] = "\#{File.dirname(__FILE__)}:\#{gems_dir}/bin:\#{ENV["PATH"]}" |
| 340 |
if (local_gem = Dir[File.join(gems_dir, "specifications", "#{spec.name}-*.gemspec")].last) |
| 341 |
version = File.basename(local_gem)[/-([\\.\\d]+)\\.gemspec$/, 1] |
| 342 |
end |
| 343 |
end |
| 344 |
|
| 345 |
version ||= "#{Gem::Requirement.default}" |
| 346 |
|
| 347 |
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then |
| 348 |
version = $1 |
| 349 |
ARGV.shift |
| 350 |
end |
| 351 |
|
| 352 |
gem '#{spec.name}', version |
| 353 |
load '#{bin_file_name}' |
| 354 |
TEXT |
| 355 |
end |
| 356 |
|
| 357 |
def find_gem_in_cache(gem, version) |
| 358 |
spec = if version |
| 359 |
version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement) |
| 360 |
Gem.source_index.find_name(gem, version).first |
| 361 |
else |
| 362 |
Gem.source_index.find_name(gem).sort_by { |g| g.version }.last |
| 363 |
end |
| 364 |
if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem") |
| 365 |
gem_file |
| 366 |
end |
| 367 |
end |
| 368 |
|
| 369 |
def update_source_index(dir) |
| 370 |
Gem.source_index.load_gems_in(File.join(dir, 'specifications')) |
| 371 |
end |
| 372 |
|
| 373 |
end |
| 374 |
|
| 375 |
############################################################################## |
| 376 |
|
| 377 |
class SourceManager |
| 378 |
|
| 379 |
include ColorfulMessages |
| 380 |
|
| 381 |
attr_accessor :source_dir |
| 382 |
|
| 383 |
def initialize(source_dir) |
| 384 |
self.source_dir = source_dir |
| 385 |
end |
| 386 |
|
| 387 |
def clone(name, url) |
| 388 |
FileUtils.cd(source_dir) do |
| 389 |
raise "destination directory already exists" if File.directory?(name) |
| 390 |
system("git clone --depth 1 #{url} #{name}") |
| 391 |
end |
| 392 |
rescue => e |
| 393 |
error "Unable to clone #{name} repository (#{e.message})" |
| 394 |
end |
| 395 |
|
| 396 |
def update(name, url) |
| 397 |
if File.directory?(repository_dir = File.join(source_dir, name)) |
| 398 |
FileUtils.cd(repository_dir) do |
| 399 |
repos = existing_repos(name) |
| 400 |
fork_name = url[/.com\/+?(.+)\/.+\.git/u, 1] |
| 401 |
if url == repos["origin"] |
| 402 |
# Pull from the original repository - no branching needed |
| 403 |
info "Pulling from origin: #{url}" |
| 404 |
system "git fetch; git checkout master; git rebase origin/master" |
| 405 |
elsif repos.values.include?(url) && fork_name |
| 406 |
# Update and switch to a remote branch for a particular github fork |
| 407 |
info "Switching to remote branch: #{fork_name}" |
| 408 |
system "git checkout -b #{fork_name} #{fork_name}/master" |
| 409 |
system "git rebase #{fork_name}/master" |
| 410 |
elsif fork_name |
| 411 |
# Create a new remote branch for a particular github fork |
| 412 |
info "Adding a new remote branch: #{fork_name}" |
| 413 |
system "git remote add -f #{fork_name} #{url}" |
| 414 |
system "git checkout -b #{fork_name} #{fork_name}/master" |
| 415 |
else |
| 416 |
warning "No valid repository found for: #{name}" |
| 417 |
end |
| 418 |
end |
| 419 |
return true |
| 420 |
else |
| 421 |
warning "No valid repository found at: #{repository_dir}" |
| 422 |
end |
| 423 |
rescue => e |
| 424 |
error "Unable to update #{name} repository (#{e.message})" |
| 425 |
return false |
| 426 |
end |
| 427 |
|
| 428 |
def existing_repos(name) |
| 429 |
repos = [] |
| 430 |
FileUtils.cd(File.join(source_dir, name)) do |
| 431 |
repos = %x[git remote -v].split("\n").map { |branch| branch.split(/\s+/) } |
| 432 |
end |
| 433 |
Hash[*repos.flatten] |
| 434 |
end |
| 435 |
|
| 436 |
end |
| 437 |
|
| 438 |
############################################################################## |
| 439 |
|
| 440 |
module MerbThorHelper |
| 441 |
|
| 442 |
attr_accessor :force_gem_dir |
| 443 |
|
| 444 |
def self.included(base) |
| 445 |
base.send(:include, ColorfulMessages) |
| 446 |
base.extend ColorfulMessages |
| 447 |
end |
| 448 |
|
| 449 |
def use_edge_gem_server |
| 450 |
::Gem.sources << 'http://edge.merbivore.com' |
| 451 |
end |
| 452 |
|
| 453 |
def source_manager |
| 454 |
@_source_manager ||= SourceManager.new(source_dir) |
| 455 |
end |
| 456 |
|
| 457 |
def extract_repositories(names) |
| 458 |
repos = [] |
| 459 |
names.each do |name| |
| 460 |
if repo_url = Merb::Source.repo(name, options[:sources]) |
| 461 |
# A repository entry for this dependency exists |
| 462 |
repo = [name, repo_url] |
| 463 |
repos << repo unless repos.include?(repo) |
| 464 |
elsif (repo_name = Merb::Stack.lookup_repository_name(name)) && |
| 465 |
(repo_url = Merb::Source.repo(repo_name, options[:sources])) |
| 466 |
# A parent repository entry for this dependency exists |
| 467 |
repo = [repo_name, repo_url] |
| 468 |
unless repos.include?(repo) |
| 469 |
puts "Found #{repo_name}/#{name} at #{repo_url}" |
| 470 |
repos << repo |
| 471 |
end |
| 472 |
end |
| 473 |
end |
| 474 |
repos |
| 475 |
end |
| 476 |
|
| 477 |
def update_dependency_repositories(dependencies) |
| 478 |
repos = extract_repositories(dependencies.map { |d| d.name }) |
| 479 |
update_repositories(repos) |
| 480 |
end |
| 481 |
|
| 482 |
def update_repositories(repos) |
| 483 |
repos.each do |(name, url)| |
| 484 |
if File.directory?(repository_dir = File.join(source_dir, name)) |
| 485 |
message "Updating or branching #{name}..." |
| 486 |
source_manager.update(name, url) |
| 487 |
else |
| 488 |
message "Cloning #{name} repository from #{url}..." |
| 489 |
source_manager.clone(name, url) |
| 490 |
end |
| 491 |
end |
| 492 |
end |
| 493 |
|
| 494 |
def install_dependency(dependency, opts = {}) |
| 495 |
version = dependency.version_requirements.to_s |
| 496 |
install_opts = default_install_options.merge(:version => version) |
| 497 |
Merb::Gem.install(dependency.name, install_opts.merge(opts)) |
| 498 |
end |
| 499 |
|
| 500 |
def install_dependency_from_source(dependency, opts = {}) |
| 501 |
matches = Dir[File.join(source_dir, "**", dependency.name, "{Rakefile,Thorfile}")] |
| 502 |
matches.reject! { |m| File.basename(m) == 'Thorfile' } |
| 503 |
if matches.length == 1 && matches[0] |
| 504 |
if File.directory?(gem_src_dir = File.dirname(matches[0])) |
| 505 |
begin |
| 506 |
Merb::Gem.install_gem_from_source(gem_src_dir, default_install_options.merge(opts)) |
| 507 |
puts "Installed #{dependency.name}" |
| 508 |
return true |
| 509 |
rescue => e |
| 510 |
warning "Unable to install #{dependency.name} from source (#{e.message})" |
| 511 |
end |
| 512 |
else |
| 513 |
msg = "Unknown directory: #{gem_src_dir}" |
| 514 |
warning "Unable to install #{dependency.name} from source (#{msg})" |
| 515 |
end |
| 516 |
elsif matches.length > 1 |
| 517 |
error "Ambigous source(s) for dependency: #{dependency.name}" |
| 518 |
matches.each { |m| puts "- #{m}" } |
| 519 |
end |
| 520 |
return false |
| 521 |
end |
| 522 |
|
| 523 |
def clobber_dependencies! |
| 524 |
if options[:force] && gem_dir && File.directory?(gem_dir) |
| 525 |
# Remove all existing local gems by clearing the gems directory |
| 526 |
if dry_run? |
| 527 |
note 'Clearing existing local gems...' |
| 528 |
else |
| 529 |
message 'Clearing existing local gems...' |
| 530 |
FileUtils.rm_rf(gem_dir) && FileUtils.mkdir_p(default_gem_dir) |
| 531 |
end |
| 532 |
elsif !local.empty? |
| 533 |
# Uninstall all local versions of the gems to install |
| 534 |
if dry_run? |
| 535 |
note 'Uninstalling existing local gems:' |
| 536 |
local.each { |gemspec| note "Uninstalled #{gemspec.name}" } |
| 537 |
else |
| 538 |
message 'Uninstalling existing local gems:' if local.size > 1 |
| 539 |
local.each do |gemspec| |
| 540 |
Merb::Gem.uninstall(gemspec.name, default_uninstall_options) |
| 541 |
end |
| 542 |
end |
| 543 |
end |
| 544 |
end |
| 545 |
|
| 546 |
def display_gemspecs(gemspecs) |
| 547 |
if gemspecs.empty? |
| 548 |
puts "- none" |
| 549 |
else |
| 550 |
gemspecs.each do |spec| |
| 551 |
if hint = Dir[File.join(spec.full_gem_path, '*.strategy')][0] |
| 552 |
strategy = File.basename(hint, '.strategy') |
| 553 |
puts "- #{spec.full_name} (#{strategy})" |
| 554 |
else |
| 555 |
puts "~ #{spec.full_name}" # unknown strategy |
| 556 |
end |
| 557 |
end |
| 558 |
end |
| 559 |
end |
| 560 |
|
| 561 |
def display_dependencies(dependencies) |
| 562 |
if dependencies.empty? |
| 563 |
puts "- none" |
| 564 |
else |
| 565 |
dependencies.each { |d| puts "- #{d.name} (#{d.version_requirements})" } |
| 566 |
end |
| 567 |
end |
| 568 |
|
| 569 |
def default_install_options |
| 570 |
{ :install_dir => gem_dir, :bin_dir => bin_dir, :ignore_dependencies => ignore_dependencies? } |
| 571 |
end |
| 572 |
|
| 573 |
def default_uninstall_options |
| 574 |
{ :install_dir => gem_dir, :bin_dir => bin_dir, :ignore => true, :all => true, :executables => true } |
| 575 |
end |
| 576 |
|
| 577 |
def dry_run? |
| 578 |
options[:"dry-run"] |
| 579 |
end |
| 580 |
|
| 581 |
def ignore_dependencies? |
| 582 |
options[:"ignore-dependencies"] |
| 583 |
end |
| 584 |
|
| 585 |
# The current working directory, or Merb app root (--merb-root option). |
| 586 |
def working_dir |
| 587 |
@_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd) |
| 588 |
end |
| 589 |
|
| 590 |
# We should have a ./src dir for local and system-wide management. |
| 591 |
def source_dir |
| 592 |
@_source_dir ||= File.join(working_dir, 'src') |
| 593 |
create_if_missing(@_source_dir) |
| 594 |
@_source_dir |
| 595 |
end |
| 596 |
|
| 597 |
# If a local ./gems dir is found, return it. |
| 598 |
def gem_dir |
| 599 |
return force_gem_dir if force_gem_dir |
| 600 |
if File.directory?(dir = default_gem_dir) |
| 601 |
dir |
| 602 |
end |
| 603 |
end |
| 604 |
|
| 605 |
def default_gem_dir |
| 606 |
File.join(working_dir, 'gems') |
| 607 |
end |
| 608 |
|
| 609 |
# If we're in a Merb app, we can have a ./bin directory; |
| 610 |
# create it if it's not there. |
| 611 |
def bin_dir |
| 612 |
@_bin_dir ||= begin |
| 613 |
if gem_dir |
| 614 |
dir = File.join(working_dir, 'bin') |
| 615 |
create_if_missing(dir) |
| 616 |
dir |
| 617 |
end |
| 618 |
end |
| 619 |
end |
| 620 |
|
| 621 |
# Helper to create dir unless it exists. |
| 622 |
def create_if_missing(path) |
| 623 |
FileUtils.mkdir(path) unless File.exists?(path) |
| 624 |
end |
| 625 |
|
| 626 |
def sudo |
| 627 |
ENV['THOR_SUDO'] ||= "sudo" |
| 628 |
sudo = Gem.win_platform? ? "" : ENV['THOR_SUDO'] |
| 629 |
end |
| 630 |
|
| 631 |
def local_gemspecs(directory = gem_dir) |
| 632 |
if File.directory?(specs_dir = File.join(directory, 'specifications')) |
| 633 |
Dir[File.join(specs_dir, '*.gemspec')].map do |gemspec_path| |
| 634 |
gemspec = Gem::Specification.load(gemspec_path) |
| 635 |
gemspec.loaded_from = gemspec_path |
| 636 |
gemspec |
| 637 |
end |
| 638 |
else |
| 639 |
[] |
| 640 |
end |
| 641 |
end |
| 642 |
|
| 643 |
end |
| 644 |
|
| 645 |
############################################################################## |
| 646 |
|
| 647 |
$SILENT = true # don't output all the mess some rake package tasks spit out |
| 648 |
|
| 649 |
module Merb |
| 650 |
|
| 651 |
class Gem < Thor |
| 652 |
|
| 653 |
include MerbThorHelper |
| 654 |
extend GemManagement |
| 655 |
|
| 656 |
attr_accessor :system, :local, :missing |
| 657 |
|
| 658 |
global_method_options = { |
| 659 |
"--merb-root" => :optional, # the directory to operate on |
| 660 |
"--version" => :optional, # gather specific version of gem |
| 661 |
"--ignore-dependencies" => :boolean # don't install sub-dependencies |
| 662 |
} |
| 663 |
|
| 664 |
method_options global_method_options |
| 665 |
def initialize(*args); super; end |
| 666 |
|
| 667 |
# List gems that match the specified criteria. |
| 668 |
# |
| 669 |
# By default all local gems are listed. When the first argument is 'all' the |
| 670 |
# list is partitioned into system an local gems; specify 'system' to show |
| 671 |
# only system gems. A second argument can be used to filter on a set of known |
| 672 |
# components, like all merb-more gems for example. |
| 673 |
# |
| 674 |
# Examples: |
| 675 |
# |
| 676 |
# merb:gem:list # list all local gems - the default |
| 677 |
# merb:gem:list all # list system and local gems |
| 678 |
# merb:gem:list system # list only system gems |
| 679 |
# merb:gem:list all merb-more # list only merb-more related gems |
| 680 |
# merb:gem:list --version 0.9.8 # list gems that match the version |
| 681 |
|
| 682 |
desc 'list [all|local|system] [comp]', 'Show installed gems' |
| 683 |
def list(filter = 'local', comp = nil) |
| 684 |
deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies |
| 685 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 686 |
case filter |
| 687 |
when 'all' |
| 688 |
message 'Installed system gems:' |
| 689 |
display_gemspecs(system) |
| 690 |
message 'Installed local gems:' |
| 691 |
display_gemspecs(local) |
| 692 |
when 'system' |
| 693 |
message 'Installed system gems:' |
| 694 |
display_gemspecs(system) |
| 695 |
when 'local' |
| 696 |
message 'Installed local gems:' |
| 697 |
display_gemspecs(local) |
| 698 |
else |
| 699 |
warning "Invalid listing filter '#{filter}'" |
| 700 |
end |
| 701 |
end |
| 702 |
|
| 703 |
# Install the specified gems. |
| 704 |
# |
| 705 |
# All arguments should be names of gems to install. |
| 706 |
# |
| 707 |
# When :force => true then any existing versions of the gems to be installed |
| 708 |
# will be uninstalled first. It's important to note that so-called meta-gems |
| 709 |
# or gems that exactly match a set of Merb::Stack.components will have their |
| 710 |
# sub-gems uninstalled too. For example, uninstalling merb-more will install |
| 711 |
# all contained gems: merb-action-args, merb-assets, merb-gen, ... |
| 712 |
# |
| 713 |
# Examples: |
| 714 |
# |
| 715 |
# merb:gem:install merb-core merb-slices # install all specified gems |
| 716 |
# merb:gem:install merb-core --version 0.9.8 # install a specific version of a gem |
| 717 |
# merb:gem:install merb-core --force # uninstall then subsequently install the gem |
| 718 |
# merb:gem:install merb-core --cache # try to install locally from system gems |
| 719 |
# merb:gem:install merb --merb-edge # install from edge.merbivore.com |
| 720 |
|
| 721 |
desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from rubygems' |
| 722 |
method_options "--cache" => :boolean, |
| 723 |
"--dry-run" => :boolean, |
| 724 |
"--force" => :boolean, |
| 725 |
"--merb-edge" => :boolean |
| 726 |
def install(*names) |
| 727 |
opts = { :version => options[:version], :cache => options[:cache] } |
| 728 |
use_edge_gem_server if options[:"merb-edge"] |
| 729 |
current_gem = nil |
| 730 |
|
| 731 |
# uninstall existing gems of the ones we're going to install |
| 732 |
uninstall(*names) if options[:force] |
| 733 |
|
| 734 |
message "Installing #{names.length} #{names.length == 1 ? 'gem' : 'gems'}..." |
| 735 |
puts "This may take a while..." |
| 736 |
|
| 737 |
names.each do |gem_name| |
| 738 |
current_gem = gem_name |
| 739 |
if dry_run? |
| 740 |
note "Installing #{current_gem}..." |
| 741 |
else |
| 742 |
message "Installing #{current_gem}..." |
| 743 |
self.class.install(gem_name, default_install_options.merge(opts)) |
| 744 |
end |
| 745 |
end |
| 746 |
rescue => e |
| 747 |
error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" |
| 748 |
end |
| 749 |
|
| 750 |
# Uninstall the specified gems. |
| 751 |
# |
| 752 |
# By default all specified gems are uninstalled. It's important to note that |
| 753 |
# so-called meta-gems or gems that match a set of Merb::Stack.components will |
| 754 |
# have their sub-gems uninstalled too. For example, uninstalling merb-more |
| 755 |
# will install all contained gems: merb-action-args, merb-assets, ... |
| 756 |
# |
| 757 |
# Existing dependencies will be clobbered; when :force => true then all gems |
| 758 |
# will be cleared, otherwise only existing local dependencies of the |
| 759 |
# matching component set will be removed. |
| 760 |
# |
| 761 |
# Examples: |
| 762 |
# |
| 763 |
# merb:gem:uninstall merb-core merb-slices # uninstall all specified gems |
| 764 |
# merb:gem:uninstall merb-core --version 0.9.8 # uninstall a specific version of a gem |
| 765 |
|
| 766 |
desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem' |
| 767 |
method_options "--dry-run" => :boolean |
| 768 |
def uninstall(*names) |
| 769 |
opts = { :version => options[:version] } |
| 770 |
current_gem = nil |
| 771 |
if dry_run? |
| 772 |
note "Uninstalling any existing gems of: #{names.join(', ')}" |
| 773 |
else |
| 774 |
message "Uninstalling any existing gems of: #{names.join(', ')}" |
| 775 |
names.each do |gem_name| |
| 776 |
current_gem = gem_name |
| 777 |
Merb::Gem.uninstall(gem_name, default_uninstall_options) rescue nil |
| 778 |
# if this gem is a meta-gem or a component set name, remove sub-gems |
| 779 |
(Merb::Stack.components(gem_name) || []).each do |comp| |
| 780 |
Merb::Gem.uninstall(comp, default_uninstall_options) rescue nil |
| 781 |
end |
| 782 |
end |
| 783 |
end |
| 784 |
rescue => e |
| 785 |
error "Failed to uninstall #{current_gem ? current_gem : 'gem'} (#{e.message})" |
| 786 |
end |
| 787 |
|
| 788 |
# Recreate all gems from gems/cache on the current platform. |
| 789 |
# |
| 790 |
# This task should be executed as part of a deployment setup, where the |
| 791 |
# deployment system runs this after the app has been installed. |
| 792 |
# Usually triggered by Capistrano, God... |
| 793 |
# |
| 794 |
# It will regenerate gems from the bundled gems cache for any gem that has |
| 795 |
# C extensions - which need to be recompiled for the target deployment platform. |
| 796 |
# |
| 797 |
# Note: at least gems/cache and gems/specifications should be in your SCM. |
| 798 |
|
| 799 |
desc 'redeploy', 'Recreate all gems on the current platform' |
| 800 |
method_options "--dry-run" => :boolean, "--force" => :boolean |
| 801 |
def redeploy |
| 802 |
require 'tempfile' # for Dir::tmpdir access |
| 803 |
if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache')) |
| 804 |
specs = local_gemspecs |
| 805 |
message "Recreating #{specs.length} gems from cache..." |
| 806 |
puts "This may take a while..." |
| 807 |
specs.each do |gemspec| |
| 808 |
if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem")) |
| 809 |
gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file)) |
| 810 |
if dry_run? |
| 811 |
note "Recreating #{gemspec.full_name}" |
| 812 |
else |
| 813 |
message "Recreating #{gemspec.full_name}" |
| 814 |
if options[:force] && File.directory?(gem = File.join(gem_dir, 'gems', gemspec.full_name)) |
| 815 |
puts "Removing existing #{gemspec.full_name}" |
| 816 |
FileUtils.rm_rf(gem) |
| 817 |
end |
| 818 |
# Copy the gem to a temporary file, because otherwise RubyGems/FileUtils |
| 819 |
# will complain about copying identical files (same source/destination). |
| 820 |
FileUtils.cp(gem_file, gem_file_copy) |
| 821 |
Merb::Gem.install(gem_file_copy, :install_dir => gem_dir, :ignore_dependencies => true) |
| 822 |
File.delete(gem_file_copy) |
| 823 |
end |
| 824 |
end |
| 825 |
end |
| 826 |
else |
| 827 |
error "No application local gems directory found" |
| 828 |
end |
| 829 |
end |
| 830 |
|
| 831 |
private |
| 832 |
|
| 833 |
# Return dependencies for all installed gems; both system-wide and locally; |
| 834 |
# optionally filters on :version requirement. |
| 835 |
def dependencies |
| 836 |
version_req = if options[:version] |
| 837 |
::Gem::Requirement.create(options[:version]) |
| 838 |
else |
| 839 |
::Gem::Requirement.default |
| 840 |
end |
| 841 |
if gem_dir |
| 842 |
::Gem.clear_paths; ::Gem.path.unshift(gem_dir) |
| 843 |
::Gem.source_index.refresh! |
| 844 |
end |
| 845 |
deps = [] |
| 846 |
::Gem.source_index.each do |fullname, gemspec| |
| 847 |
if version_req.satisfied_by?(gemspec.version) |
| 848 |
deps << ::Gem::Dependency.new(gemspec.name, "= #{gemspec.version}") |
| 849 |
end |
| 850 |
end |
| 851 |
::Gem.clear_paths if gem_dir |
| 852 |
deps.sort |
| 853 |
end |
| 854 |
|
| 855 |
public |
| 856 |
|
| 857 |
# Install gem with some default options. |
| 858 |
def self.install(name, options = {}) |
| 859 |
defaults = {} |
| 860 |
defaults[:cache] = false unless opts[:install_dir] |
| 861 |
install_gem(name, defaults.merge(options)) |
| 862 |
end |
| 863 |
|
| 864 |
# Uninstall gem with some default options. |
| 865 |
def self.uninstall(name, options = {}) |
| 866 |
defaults = { :ignore => true, :executables => true } |
| 867 |
uninstall_gem(name, defaults.merge(options)) |
| 868 |
end |
| 869 |
|
| 870 |
end |
| 871 |
|
| 872 |
class Tasks < Thor |
| 873 |
|
| 874 |
include MerbThorHelper |
| 875 |
|
| 876 |
# Show merb.thor version information |
| 877 |
# |
| 878 |
# merb:tasks:version # show the current version info |
| 879 |
# merb:tasks:version --info # show extended version info |
| 880 |
|
| 881 |
desc 'version', 'Show verion info' |
| 882 |
method_options "--info" => :boolean |
| 883 |
def version |
| 884 |
message "Currently installed merb.thor version: #{MERB_THOR_VERSION}" |
| 885 |
if options[:version] |
| 886 |
self.options = { :"dry-run" => true } |
| 887 |
self.update # run update task with dry-run enabled |
| 888 |
end |
| 889 |
end |
| 890 |
|
| 891 |
# Update merb.thor tasks from remotely available version |
| 892 |
# |
| 893 |
# merb:tasks:update # update merb.thor |
| 894 |
# merb:tasks:update --force # force-update merb.thor |
| 895 |
# merb:tasks:update --dry-run # show version info only |
| 896 |
|
| 897 |
desc 'update [URL]', 'Fetch the latest merb.thor and install it locally' |
| 898 |
method_options "--dry-run" => :boolean, "--force" => :boolean |
| 899 |
def update(url = 'http://merbivore.com/merb.thor') |
| 900 |
require 'open-uri' |
| 901 |
require 'rubygems/version' |
| 902 |
remote_file = open(url) |
| 903 |
code = remote_file.read |
| 904 |
|
| 905 |
# Extract version information from the source code |
| 906 |
if version = code[/^MERB_THOR_VERSION\s?=\s?('|")([\.\d]+)('|")/,2] |
| 907 |
# borrow version comparison from rubygems' Version class |
| 908 |
current_version = ::Gem::Version.new(MERB_THOR_VERSION) |
| 909 |
remote_version = ::Gem::Version.new(version) |
| 910 |
|
| 911 |
if current_version >= remote_version |
| 912 |
puts "currently installed: #{current_version}" |
| 913 |
if current_version != remote_version |
| 914 |
puts "available version: #{remote_version}" |
| 915 |
end |
| 916 |
info "No update of merb.thor necessary#{options[:force] ? ' (forced)' : ''}" |
| 917 |
proceed = options[:force] |
| 918 |
elsif current_version < remote_version |
| 919 |
puts "currently installed: #{current_version}" |
| 920 |
puts "available version: #{remote_version}" |
| 921 |
proceed = true |
| 922 |
end |
| 923 |
|
| 924 |
if proceed && !dry_run? |
| 925 |
File.open(File.join(__FILE__), 'w') do |f| |
| 926 |
f.write(code) |
| 927 |
end |
| 928 |
success "Installed the latest merb.thor (v#{version})" |
| 929 |
end |
| 930 |
else |
| 931 |
raise "invalid source-code data" |
| 932 |
end |
| 933 |
rescue OpenURI::HTTPError |
| 934 |
error "Error opening #{url}" |
| 935 |
rescue => e |
| 936 |
error "An error occurred (#{e.message})" |
| 937 |
end |
| 938 |
|
| 939 |
end |
| 940 |
|
| 941 |
#### MORE LOW-LEVEL TASKS #### |
| 942 |
|
| 943 |
class Source < Thor |
| 944 |
|
| 945 |
group 'core' |
| 946 |
|
| 947 |
include MerbThorHelper |
| 948 |
extend GemManagement |
| 949 |
|
| 950 |
attr_accessor :system, :local, :missing |
| 951 |
|
| 952 |
global_method_options = { |
| 953 |
"--merb-root" => :optional, # the directory to operate on |
| 954 |
"--ignore-dependencies" => :boolean, # don't install sub-dependencies |
| 955 |
"--sources" => :optional # a yml config to grab sources from |
| 956 |
} |
| 957 |
|
| 958 |
method_options global_method_options |
| 959 |
def initialize(*args); super; end |
| 960 |
|
| 961 |
# List source repositories, of either local or known sources. |
| 962 |
# |
| 963 |
# Examples: |
| 964 |
# |
| 965 |
# merb:source:list # list all local sources |
| 966 |
# merb:source:list available # list all known sources |
| 967 |
|
| 968 |
desc 'list [local|available]', 'Show git source repositories' |
| 969 |
def list(mode = 'local') |
| 970 |
if mode == 'available' |
| 971 |
message 'Available source repositories:' |
| 972 |
repos = self.class.repos(options[:sources]) |
| 973 |
repos.keys.sort.each { |name| puts "- #{name}: #{repos[name]}" } |
| 974 |
elsif mode == 'local' |
| 975 |
message 'Current source repositories:' |
| 976 |
Dir[File.join(source_dir, '*')].each do |src| |
| 977 |
next unless File.directory?(src) |
| 978 |
src_name = File.basename(src) |
| 979 |
unless (repos = source_manager.existing_repos(src_name)).empty? |
| 980 |
puts "#{src_name}" |
| 981 |
repos.keys.sort.each { |b| puts "- #{b}: #{repos[b]}" } |
| 982 |
end |
| 983 |
end |
| 984 |
else |
| 985 |
error "Unknown listing: #{mode}" |
| 986 |
end |
| 987 |
end |
| 988 |
|
| 989 |
# Install the specified gems. |
| 990 |
# |
| 991 |
# All arguments should be names of gems to install. |
| 992 |
# |
| 993 |
# When :force => true then any existing versions of the gems to be installed |
| 994 |
# will be uninstalled first. It's important to note that so-called meta-gems |
| 995 |
# or gems that exactly match a set of Merb::Stack.components will have their |
| 996 |
# sub-gems uninstalled too. For example, uninstalling merb-more will install |
| 997 |
# all contained gems: merb-action-args, merb-assets, merb-gen, ... |
| 998 |
# |
| 999 |
# Examples: |
| 1000 |
# |
| 1001 |
# merb:source:install merb-core merb-slices # install all specified gems |
| 1002 |
# merb:source:install merb-core --force # uninstall then subsequently install the gem |
| 1003 |
# merb:source:install merb-core --wipe # clear repo then install the gem |
| 1004 |
|
| 1005 |
desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from git source/edge' |
| 1006 |
method_options "--dry-run" => :boolean, |
| 1007 |
"--force" => :boolean, |
| 1008 |
"--wipe" => :boolean |
| 1009 |
def install(*names) |
| 1010 |
use_edge_gem_server |
| 1011 |
# uninstall existing gems of the ones we're going to install |
| 1012 |
uninstall(*names) if options[:force] || options[:wipe] |
| 1013 |
|
| 1014 |
# We want dependencies instead of just names |
| 1015 |
deps = names.map { |n| ::Gem::Dependency.new(n, ::Gem::Requirement.default) } |
| 1016 |
|
| 1017 |
# Selectively update repositories for the matching dependencies |
| 1018 |
update_dependency_repositories(deps) unless dry_run? |
| 1019 |
|
| 1020 |
current_gem = nil |
| 1021 |
deps.each do |dependency| |
| 1022 |
current_gem = dependency.name |
| 1023 |
if dry_run? |
| 1024 |
note "Installing #{current_gem} from source..." |
| 1025 |
else |
| 1026 |
message "Installing #{current_gem} from source..." |
| 1027 |
puts "This may take a while..." |
| 1028 |
unless install_dependency_from_source(dependency) |
| 1029 |
raise "gem source not found" |
| 1030 |
end |
| 1031 |
end |
| 1032 |
end |
| 1033 |
rescue => e |
| 1034 |
error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})" |
| 1035 |
end |
| 1036 |
|
| 1037 |
# Uninstall the specified gems. |
| 1038 |
# |
| 1039 |
# By default all specified gems are uninstalled. It's important to note that |
| 1040 |
# so-called meta-gems or gems that match a set of Merb::Stack.components will |
| 1041 |
# have their sub-gems uninstalled too. For example, uninstalling merb-more |
| 1042 |
# will install all contained gems: merb-action-args, merb-assets, ... |
| 1043 |
# |
| 1044 |
# Existing dependencies will be clobbered; when :force => true then all gems |
| 1045 |
# will be cleared, otherwise only existing local dependencies of the |
| 1046 |
# matching component set will be removed. Additionally when :wipe => true, |
| 1047 |
# the matching git repositories will be removed from the source directory. |
| 1048 |
# |
| 1049 |
# Examples: |
| 1050 |
# |
| 1051 |
# merb:source:uninstall merb-core merb-slices # uninstall all specified gems |
| 1052 |
# merb:source:uninstall merb-core --wipe # force-uninstall a gem and clear repo |
| 1053 |
|
| 1054 |
desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem (specify --force to remove the repo)' |
| 1055 |
method_options "--version" => :optional, "--dry-run" => :boolean, "--wipe" => :boolean |
| 1056 |
def uninstall(*names) |
| 1057 |
# Remove the repos that contain the gem |
| 1058 |
if options[:wipe] |
| 1059 |
extract_repositories(names).each do |(name, url)| |
| 1060 |
if File.directory?(src = File.join(source_dir, name)) |
| 1061 |
if dry_run? |
| 1062 |
note "Removing #{src}..." |
| 1063 |
else |
| 1064 |
info "Removing #{src}..." |
| 1065 |
FileUtils.rm_rf(src) |
| 1066 |
end |
| 1067 |
end |
| 1068 |
end |
| 1069 |
end |
| 1070 |
|
| 1071 |
# Use the Merb::Gem#uninstall task to handle this |
| 1072 |
gem_tasks = Merb::Gem.new |
| 1073 |
gem_tasks.options = options |
| 1074 |
gem_tasks.uninstall(*names) |
| 1075 |
end |
| 1076 |
|
| 1077 |
# Update the specified source repositories. |
| 1078 |
# |
| 1079 |
# The arguments can be actual repository names (from Merb::Source.repos) |
| 1080 |
# or names of known merb stack gems. If the repo doesn't exist already, |
| 1081 |
# it will be created and cloned. |
| 1082 |
# |
| 1083 |
# merb:source:pull merb-core # update source of specified gem |
| 1084 |
# merb:source:pull merb-slices # implicitly updates merb-more |
| 1085 |
|
| 1086 |
desc 'pull REPO_NAME [GEM_NAME, ...]', 'Update git source repository from edge' |
| 1087 |
def pull(*names) |
| 1088 |
repos = extract_repositories(names) |
| 1089 |
update_repositories(repos) |
| 1090 |
unless repos.empty? |
| 1091 |
message "Updated the following repositories:" |
| 1092 |
repos.each { |name, url| puts "- #{name}: #{url}" } |
| 1093 |
else |
| 1094 |
warning "No repositories found to update!" |
| 1095 |
end |
| 1096 |
end |
| 1097 |
|
| 1098 |
# Clone a git repository into ./src. |
| 1099 |
|
| 1100 |
# The repository can be a direct git url or a known -named- repository. |
| 1101 |
# |
| 1102 |
# Examples: |
| 1103 |
# |
| 1104 |
# merb:source:clone merb-core |
| 1105 |
# merb:source:clone dm-core awesome-repo |
| 1106 |
# merb:source:clone dm-core --sources ./path/to/sources.yml |
| 1107 |
# merb:source:clone git://github.com/sam/dm-core.git |
| 1108 |
|
| 1109 |
desc 'clone (REPO_NAME|URL) [DIR_NAME]', 'Clone git source repository by name or url' |
| 1110 |
def clone(repository, name = nil) |
| 1111 |
if repository =~ /^git:\/\// |
| 1112 |
repository_url = repository |
| 1113 |
repository_name = File.basename(repository_url, '.git') |
| 1114 |
elsif url = Merb::Source.repo(repository, options[:sources]) |
| 1115 |
repository_url = url |
| 1116 |
repository_name = repository |
| 1117 |
end |
| 1118 |
source_manager.clone(name || repository_name, repository_url) |
| 1119 |
end |
| 1120 |
|
| 1121 |
# Git repository sources - pass source_config option to load a yaml |
| 1122 |
# configuration file - defaults to ./config/git-sources.yml and |
| 1123 |
# ~/.merb/git-sources.yml - which you need to create yourself. |
| 1124 |
# |
| 1125 |
# Example of contents: |
| 1126 |
# |
| 1127 |
# merb-core: git://github.com/myfork/merb-core.git |
| 1128 |
# merb-more: git://github.com/myfork/merb-more.git |
| 1129 |
|
| 1130 |
def self.repos(source_config = nil) |
| 1131 |
source_config ||= begin |
| 1132 |
local_config = File.join(Dir.pwd, 'config', 'git-sources.yml') |
| 1133 |
user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml') |
| 1134 |
File.exists?(local_config) ? local_config : user_config |
| 1135 |
end |
| 1136 |
if source_config && File.exists?(source_config) |
| 1137 |
default_repos.merge(YAML.load(File.read(source_config))) |
| 1138 |
else |
| 1139 |
default_repos |
| 1140 |
end |
| 1141 |
end |
| 1142 |
|
| 1143 |
def self.repo(name, source_config = nil) |
| 1144 |
self.repos(source_config)[name] |
| 1145 |
end |
| 1146 |
|
| 1147 |
# Default Git repositories |
| 1148 |
def self.default_repos |
| 1149 |
@_default_repos ||= { |
| 1150 |
'merb' => "git://github.com/wycats/merb.git", |
| 1151 |
'merb-plugins' => "git://github.com/wycats/merb-plugins.git", |
| 1152 |
'extlib' => "git://github.com/sam/extlib.git", |
| 1153 |
'dm-core' => "git://github.com/sam/dm-core.git", |
| 1154 |
'dm-more' => "git://github.com/sam/dm-more.git", |
| 1155 |
'sequel' => "git://github.com/wayneeseguin/sequel.git", |
| 1156 |
'do' => "git://github.com/sam/do.git", |
| 1157 |
'thor' => "git://github.com/wycats/thor.git", |
| 1158 |
'rake' => "git://github.com/jimweirich/rake.git" |
| 1159 |
} |
| 1160 |
end |
| 1161 |
|
| 1162 |
end |
| 1163 |
|
| 1164 |
class Dependencies < Thor |
| 1165 |
|
| 1166 |
group 'core' |
| 1167 |
|
| 1168 |
# The Dependencies tasks will install dependencies based on actual application |
| 1169 |
# dependencies. For this, the application is queried for any dependencies. |
| 1170 |
# All operations will be performed within this context. |
| 1171 |
|
| 1172 |
attr_accessor :system, :local, :missing, :extract_dependencies |
| 1173 |
|
| 1174 |
include MerbThorHelper |
| 1175 |
|
| 1176 |
global_method_options = { |
| 1177 |
"--merb-root" => :optional, # the directory to operate on |
| 1178 |
"--ignore-dependencies" => :boolean, # ignore sub-dependencies |
| 1179 |
"--stack" => :boolean, # gather only stack dependencies |
| 1180 |
"--no-stack" => :boolean, # gather only non-stack dependencies |
| 1181 |
"--extract" => :boolean, # gather dependencies from the app itself |
| 1182 |
"--config-file" => :optional, # gather from the specified yaml config |
| 1183 |
"--version" => :optional # gather specific version of framework |
| 1184 |
} |
| 1185 |
|
| 1186 |
method_options global_method_options |
| 1187 |
def initialize(*args); super; end |
| 1188 |
|
| 1189 |
# List application dependencies. |
| 1190 |
# |
| 1191 |
# By default all dependencies are listed, partitioned into system, local and |
| 1192 |
# currently missing dependencies. The first argument allows you to filter |
| 1193 |
# on any of the partitionings. A second argument can be used to filter on |
| 1194 |
# a set of known components, like all merb-more gems for example. |
| 1195 |
# |
| 1196 |
# Examples: |
| 1197 |
# |
| 1198 |
# merb:dependencies:list # list all dependencies - the default |
| 1199 |
# merb:dependencies:list local # list only local gems |
| 1200 |
# merb:dependencies:list all merb-more # list only merb-more related dependencies |
| 1201 |
# merb:dependencies:list --stack # list framework dependencies |
| 1202 |
# merb:dependencies:list --no-stack # list 3rd party dependencies |
| 1203 |
# merb:dependencies:list --extract # list dependencies by extracting them |
| 1204 |
# merb:dependencies:list --config-file file.yml # list from the specified config file |
| 1205 |
|
| 1206 |
desc 'list [all|local|system|missing] [comp]', 'Show application dependencies' |
| 1207 |
def list(filter = 'all', comp = nil) |
| 1208 |
deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies |
| 1209 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1210 |
case filter |
| 1211 |
when 'all' |
| 1212 |
message 'Installed system gem dependencies:' |
| 1213 |
display_gemspecs(system) |
| 1214 |
message 'Installed local gem dependencies:' |
| 1215 |
display_gemspecs(local) |
| 1216 |
unless missing.empty? |
| 1217 |
error 'Missing gem dependencies:' |
| 1218 |
display_dependencies(missing) |
| 1219 |
end |
| 1220 |
when 'system' |
| 1221 |
message 'Installed system gem dependencies:' |
| 1222 |
display_gemspecs(system) |
| 1223 |
when 'local' |
| 1224 |
message 'Installed local gem dependencies:' |
| 1225 |
display_gemspecs(local) |
| 1226 |
when 'missing' |
| 1227 |
error 'Missing gem dependencies:' |
| 1228 |
display_dependencies(missing) |
| 1229 |
else |
| 1230 |
warning "Invalid listing filter '#{filter}'" |
| 1231 |
end |
| 1232 |
if missing.size > 0 |
| 1233 |
info "Some dependencies are currently missing!" |
| 1234 |
elsif local.size == deps.size |
| 1235 |
info "All dependencies have been bundled with the application." |
| 1236 |
elsif local.size > system.size |
| 1237 |
info "Most dependencies have been bundled with the application." |
| 1238 |
elsif system.size > 0 && local.size > 0 |
| 1239 |
info "Some dependencies have been bundled with the application." |
| 1240 |
elsif local.empty? && system.size == deps.size |
| 1241 |
info "All dependencies are available on the system." |
| 1242 |
end |
| 1243 |
end |
| 1244 |
|
| 1245 |
# Install application dependencies. |
| 1246 |
# |
| 1247 |
# By default all required dependencies are installed. The first argument |
| 1248 |
# specifies which strategy to use: stable or edge. A second argument can be |
| 1249 |
# used to filter on a set of known components. |
| 1250 |
# |
| 1251 |
# Existing dependencies will be clobbered; when :force => true then all gems |
| 1252 |
# will be cleared first, otherwise only existing local dependencies of the |
| 1253 |
# gems to be installed will be removed. |
| 1254 |
# |
| 1255 |
# Examples: |
| 1256 |
# |
| 1257 |
# merb:dependencies:install # install all dependencies using stable strategy |
| 1258 |
# merb:dependencies:install stable --version 0.9.8 # install a specific version of the framework |
| 1259 |
# merb:dependencies:install stable missing # install currently missing gems locally |
| 1260 |
# merb:dependencies:install stable merb-more # install only merb-more related dependencies |
| 1261 |
# merb:dependencies:install stable --stack # install framework dependencies |
| 1262 |
# merb:dependencies:install stable --no-stack # install 3rd party dependencies |
| 1263 |
# merb:dependencies:install stable --extract # extract dependencies from the actual app |
| 1264 |
# merb:dependencies:install stable --config-file file.yml # read from the specified config file |
| 1265 |
# |
| 1266 |
# In addition to the options above, edge install uses the following: |
| 1267 |
# |
| 1268 |
# merb:dependencies:install edge # install all dependencies using edge strategy |
| 1269 |
# merb:dependencies:install edge --sources file.yml # install edge from the specified git sources config |
| 1270 |
|
| 1271 |
desc 'install [stable|edge] [comp]', 'Install application dependencies' |
| 1272 |
method_options "--sources" => :optional, # only for edge strategy |
| 1273 |
"--local" => :boolean, # force local install |
| 1274 |
"--dry-run" => :boolean, |
| 1275 |
"--force" => :boolean |
| 1276 |
def install(strategy = 'stable', comp = nil) |
| 1277 |
if strategy?(strategy) |
| 1278 |
# Force local dependencies by creating ./gems before proceeding |
| 1279 |
create_if_missing(default_gem_dir) if options[:local] |
| 1280 |
|
| 1281 |
where = gem_dir ? 'locally' : 'system-wide' |
| 1282 |
|
| 1283 |
# When comp == 'missing' then filter on missing dependencies |
| 1284 |
if only_missing = comp == 'missing' |
| 1285 |
message "Preparing to install missing gems #{where} using #{strategy} strategy..." |
| 1286 |
comp = nil |
| 1287 |
clobber = false |
| 1288 |
else |
| 1289 |
message "Preparing to install #{where} using #{strategy} strategy..." |
| 1290 |
clobber = true |
| 1291 |
end |
| 1292 |
|
| 1293 |
# If comp given, filter on known stack components |
| 1294 |
deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies |
| 1295 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1296 |
|
| 1297 |
# Only install currently missing gems (for comp == missing) |
| 1298 |
if only_missing |
| 1299 |
deps.reject! { |dep| not missing.include?(dep) } |
| 1300 |
end |
| 1301 |
|
| 1302 |
if deps.empty? |
| 1303 |
warning "No dependencies to install..." |
| 1304 |
else |
| 1305 |
puts "#{deps.length} dependencies to install..." |
| 1306 |
puts "This may take a while..." |
| 1307 |
install_dependencies(strategy, deps, clobber) |
| 1308 |
end |
| 1309 |
|
| 1310 |
# Show current dependency info now that we're done |
| 1311 |
puts # Seperate output |
| 1312 |
list('local', comp) |
| 1313 |
else |
| 1314 |
warning "Invalid install strategy '#{strategy}'" |
| 1315 |
puts |
| 1316 |
message "Please choose one of the following installation strategies: stable or edge:" |
| 1317 |
puts "$ thor merb:dependencies:install stable" |
| 1318 |
puts "$ thor merb:dependencies:install edge" |
| 1319 |
end |
| 1320 |
end |
| 1321 |
|
| 1322 |
# Uninstall application dependencies. |
| 1323 |
# |
| 1324 |
# By default all required dependencies are installed. An optional argument |
| 1325 |
# can be used to filter on a set of known components. |
| 1326 |
# |
| 1327 |
# Existing dependencies will be clobbered; when :force => true then all gems |
| 1328 |
# will be cleared, otherwise only existing local dependencies of the |
| 1329 |
# matching component set will be removed. |
| 1330 |
# |
| 1331 |
# Examples: |
| 1332 |
# |
| 1333 |
# merb:dependencies:uninstall # uninstall all dependencies - the default |
| 1334 |
# merb:dependencies:uninstall merb-more # uninstall merb-more related gems locally |
| 1335 |
|
| 1336 |
desc 'uninstall [comp]', 'Uninstall application dependencies' |
| 1337 |
method_options "--dry-run" => :boolean, "--force" => :boolean |
| 1338 |
def uninstall(comp = nil) |
| 1339 |
# If comp given, filter on known stack components |
| 1340 |
deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies |
| 1341 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1342 |
# Clobber existing local dependencies - based on self.local |
| 1343 |
clobber_dependencies! |
| 1344 |
end |
| 1345 |
|
| 1346 |
# Recreate all gems from gems/cache on the current platform. |
| 1347 |
# |
| 1348 |
# Note: use merb:gem:redeploy instead |
| 1349 |
|
| 1350 |
desc 'redeploy', 'Recreate all gems on the current platform' |
| 1351 |
method_options "--dry-run" => :boolean, "--force" => :boolean |
| 1352 |
def redeploy |
| 1353 |
warning 'Warning: merb:dependencies:redeploy has been deprecated - use merb:gem:redeploy instead' |
| 1354 |
gem = Merb::Gem.new |
| 1355 |
gem.options = options |
| 1356 |
gem.redeploy |
| 1357 |
end |
| 1358 |
|
| 1359 |
# Create a dependencies configuration file. |
| 1360 |
# |
| 1361 |
# A configuration yaml file will be created from the extracted application |
| 1362 |
# dependencies. The format of the configuration is as follows: |
| 1363 |
# |
| 1364 |
# --- |
| 1365 |
# - merb-core (= 0.9.8, runtime) |
| 1366 |
# - merb-slices (= 0.9.8, runtime) |
| 1367 |
# |
| 1368 |
# This format is exactly the same as Gem::Dependency#to_s returns. |
| 1369 |
# |
| 1370 |
# Examples: |
| 1371 |
# |
| 1372 |
# merb:dependencies:configure --force # overwrite the default config file |
| 1373 |
# merb:dependencies:configure --version 0.9.8 # configure specific framework version |
| 1374 |
# merb:dependencies:configure --config-file file.yml # write to the specified config file |
| 1375 |
|
| 1376 |
desc 'configure [comp]', 'Create a dependencies config file' |
| 1377 |
method_options "--dry-run" => :boolean, "--force" => :boolean, "--versions" => :boolean |
| 1378 |
def configure(comp = nil) |
| 1379 |
self.extract_dependencies = true # of course we need to consult the app itself |
| 1380 |
# If comp given, filter on known stack components |
| 1381 |
deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies |
| 1382 |
|
| 1383 |
# If --versions is set, update the version_requirements with the actual version available |
| 1384 |
if options[:versions] |
| 1385 |
specs = local_gemspecs |
| 1386 |
deps.each do |dep| |
| 1387 |
if spec = specs.find { |s| s.name == dep.name } |
| 1388 |
dep.version_requirements = ::Gem::Requirement.create(spec.version) |
| 1389 |
end |
| 1390 |
end |
| 1391 |
end |
| 1392 |
|
| 1393 |
config = YAML.dump(deps.map { |d| d.to_s }) |
| 1394 |
puts "#{config}\n" |
| 1395 |
if File.exists?(config_file) && !options[:force] |
| 1396 |
error "File already exists! Use --force to overwrite." |
| 1397 |
else |
| 1398 |
if dry_run? |
| 1399 |
note "Written #{config_file}" |
| 1400 |
else |
| 1401 |
FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir) |
| 1402 |
File.open(config_file, 'w') { |f| f.write config } |
| 1403 |
success "Written #{config_file}" |
| 1404 |
end |
| 1405 |
end |
| 1406 |
rescue |
| 1407 |
error "Failed to write to #{config_file}" |
| 1408 |
end |
| 1409 |
|
| 1410 |
### Helper Methods |
| 1411 |
|
| 1412 |
def strategy?(strategy) |
| 1413 |
if self.respond_to?(method = :"#{strategy}_strategy", true) |
| 1414 |
method |
| 1415 |
end |
| 1416 |
end |
| 1417 |
|
| 1418 |
def install_dependencies(strategy, deps, clobber = true) |
| 1419 |
if method = strategy?(strategy) |
| 1420 |
# Clobber existing local dependencies |
| 1421 |
clobber_dependencies! if clobber |
| 1422 |
|
| 1423 |
# Run the chosen strategy - collect files installed from stable gems |
| 1424 |
installed_from_stable = send(method, deps).map { |d| d.name } |
| 1425 |
|
| 1426 |
unless dry_run? |
| 1427 |
# Sleep a bit otherwise the following steps won't see the new files |
| 1428 |
sleep(deps.length) if deps.length > 0 && deps.length <= 10 |
| 1429 |
|
| 1430 |
# Leave a file to denote the strategy that has been used for this dependency |
| 1431 |
self.local.each do |spec| |
| 1432 |
next unless File.directory?(spec.full_gem_path) |
| 1433 |
unless installed_from_stable.include?(spec.name) |
| 1434 |
FileUtils.touch(File.join(spec.full_gem_path, "#{strategy}.strategy")) |
| 1435 |
else |
| 1436 |
FileUtils.touch(File.join(spec.full_gem_path, "stable.strategy")) |
| 1437 |
end |
| 1438 |
end |
| 1439 |
end |
| 1440 |
return true |
| 1441 |
end |
| 1442 |
false |
| 1443 |
end |
| 1444 |
|
| 1445 |
def dependencies |
| 1446 |
if extract_dependencies? |
| 1447 |
# Extract dependencies from the current application |
| 1448 |
deps = Merb::Stack.core_dependencies(gem_dir, ignore_dependencies?) |
| 1449 |
deps += Merb::Dependencies.extract_dependencies(working_dir) |
| 1450 |
else |
| 1451 |
# Use preconfigured dependencies from yaml file |
| 1452 |
deps = config_dependencies |
| 1453 |
end |
| 1454 |
|
| 1455 |
stack_components = Merb::Stack.components |
| 1456 |
|
| 1457 |
if options[:stack] |
| 1458 |
# Limit to stack components only |
| 1459 |
deps.reject! { |dep| not stack_components.include?(dep.name) } |
| 1460 |
elsif options[:"no-stack"] |
| 1461 |
# Limit to non-stack components |
| 1462 |
deps.reject! { |dep| stack_components.include?(dep.name) } |
| 1463 |
end |
| 1464 |
|
| 1465 |
if options[:version] |
| 1466 |
version_req = ::Gem::Requirement.create("= #{options[:version]}") |
| 1467 |
elsif core = deps.find { |d| d.name == 'merb-core' } |
| 1468 |
version_req = core.version_requirements |
| 1469 |
end |
| 1470 |
|
| 1471 |
if version_req |
| 1472 |
# Handle specific version requirement for framework components |
| 1473 |
framework_components = Merb::Stack.framework_components |
| 1474 |
deps.each do |dep| |
| 1475 |
if framework_components.include?(dep.name) |
| 1476 |
dep.version_requirements = version_req |
| 1477 |
end |
| 1478 |
end |
| 1479 |
end |
| 1480 |
|
| 1481 |
deps |
| 1482 |
end |
| 1483 |
|
| 1484 |
def config_dependencies |
| 1485 |
if File.exists?(config_file) |
| 1486 |
self.class.parse_dependencies_yaml(File.read(config_file)) |
| 1487 |
else |
| 1488 |
warning "No dependencies.yml file found at: #{config_file}" |
| 1489 |
[] |
| 1490 |
end |
| 1491 |
end |
| 1492 |
|
| 1493 |
def extract_dependencies? |
| 1494 |
options[:extract] || extract_dependencies |
| 1495 |
end |
| 1496 |
|
| 1497 |
def config_file |
| 1498 |
@config_file ||= begin |
| 1499 |
options[:"config-file"] || File.join(working_dir, 'config', 'dependencies.yml') |
| 1500 |
end |
| 1501 |
end |
| 1502 |
|
| 1503 |
def config_dir |
| 1504 |
File.dirname(config_file) |
| 1505 |
end |
| 1506 |
|
| 1507 |
### Strategy handlers |
| 1508 |
|
| 1509 |
private |
| 1510 |
|
| 1511 |
def stable_strategy(deps) |
| 1512 |
installed_from_rubygems = [] |
| 1513 |
if core = deps.find { |d| d.name == 'merb-core' } |
| 1514 |
if dry_run? |
| 1515 |
note "Installing #{core.name}..." |
| 1516 |
else |
| 1517 |
if install_dependency(core) |
| 1518 |
installed_from_rubygems << core |
| 1519 |
else |
| 1520 |
msg = "Try specifying a lower version of merb-core with --version" |
| 1521 |
if version_no = core.version_requirements.to_s[/([\.\d]+)$/, 1] |
| 1522 |
num = "%03d" % (version_no.gsub('.', '').to_i - 1) |
| 1523 |
puts "The required version (#{version_no}) probably isn't available as a stable rubygem yet." |
| 1524 |
info "#{msg} #{num.split(//).join('.')}" |
| 1525 |
else |
| 1526 |
puts "The required version probably isn't available as a stable rubygem yet." |
| 1527 |
info msg |
| 1528 |
end |
| 1529 |
end |
| 1530 |
end |
| 1531 |
end |
| 1532 |
|
| 1533 |
deps.each do |dependency| |
| 1534 |
next if dependency.name == 'merb-core' |
| 1535 |
if dry_run? |
| 1536 |
note "Installing #{dependency.name}..." |
| 1537 |
else |
| 1538 |
install_dependency(dependency) |
| 1539 |
installed_from_rubygems << dependency |
| 1540 |
end |
| 1541 |
end |
| 1542 |
installed_from_rubygems |
| 1543 |
end |
| 1544 |
|
| 1545 |
def edge_strategy(deps) |
| 1546 |
use_edge_gem_server |
| 1547 |
installed_from_rubygems = [] |
| 1548 |
|
| 1549 |
# Selectively update repositories for the matching dependencies |
| 1550 |
update_dependency_repositories(deps) unless dry_run? |
| 1551 |
|
| 1552 |
if core = deps.find { |d| d.name == 'merb-core' } |
| 1553 |
if dry_run? |
| 1554 |
note "Installing #{core.name}..." |
| 1555 |
else |
| 1556 |
if install_dependency_from_source(core) |
| 1557 |
elsif install_dependency(core) |
| 1558 |
info "Installed #{core.name} from rubygems..." |
| 1559 |
installed_from_rubygems << core |
| 1560 |
end |
| 1561 |
end |
| 1562 |
end |
| 1563 |
|
| 1564 |
deps.each do |dependency| |
| 1565 |
next if dependency.name == 'merb-core' |
| 1566 |
if dry_run? |
| 1567 |
note "Installing #{dependency.name}..." |
| 1568 |
else |
| 1569 |
if install_dependency_from_source(dependency) |
| 1570 |
elsif install_dependency(dependency) |
| 1571 |
info "Installed #{dependency.name} from rubygems..." |
| 1572 |
installed_from_rubygems << dependency |
| 1573 |
end |
| 1574 |
end |
| 1575 |
end |
| 1576 |
|
| 1577 |
installed_from_rubygems |
| 1578 |
end |
| 1579 |
|
| 1580 |
### Class Methods |
| 1581 |
|
| 1582 |
public |
| 1583 |
|
| 1584 |
def self.list(filter = 'all', comp = nil, options = {}) |
| 1585 |
instance = Merb::Dependencies.new |
| 1586 |
instance.options = options |
| 1587 |
instance.list(filter, comp) |
| 1588 |
end |
| 1589 |
|
| 1590 |
# Extract application dependencies by querying the app directly. |
| 1591 |
def self.extract_dependencies(merb_root) |
| 1592 |
require 'merb-core' |
| 1593 |
if !@_merb_loaded || Merb.root != merb_root |
| 1594 |
Merb.start_environment( |
| 1595 |
:log_level => :fatal, |
| 1596 |
:testing => true, |
| 1597 |
:adapter => 'runner', |
| 1598 |
:environment => ENV['MERB_ENV'] || 'development', |
| 1599 |
:merb_root => merb_root |
| 1600 |
) |
| 1601 |
@_merb_loaded = true |
| 1602 |
end |
| 1603 |
Merb::BootLoader::Dependencies.dependencies |
| 1604 |
rescue StandardError => e |
| 1605 |
error "Couldn't extract dependencies from application!" |
| 1606 |
error e.message |
| 1607 |
puts "Make sure you're executing the task from your app (--merb-root)" |
| 1608 |
return [] |
| 1609 |
rescue SystemExit |
| 1610 |
error "Couldn't extract dependencies from application!" |
| 1611 |
error "application failed to run" |
| 1612 |
puts "Please check if your application runs using 'merb'; for example," |
| 1613 |
puts "look for any gem version mismatches in dependencies.rb" |
| 1614 |
return [] |
| 1615 |
end |
| 1616 |
|
| 1617 |
# Parse the basic YAML config data, and process Gem::Dependency output. |
| 1618 |
# Formatting example: merb_helpers (>= 0.9.8, runtime) |
| 1619 |
def self.parse_dependencies_yaml(yaml) |
| 1620 |
dependencies = [] |
| 1621 |
entries = YAML.load(yaml) rescue [] |
| 1622 |
entries.each do |entry| |
| 1623 |
if matches = entry.match(/^(\S+) \(([^,]+)?, ([^\)]+)\)/) |
| 1624 |
name, version_req, type = matches.captures |
| 1625 |
dependencies << ::Gem::Dependency.new(name, version_req, type.to_sym) |
| 1626 |
else |
| 1627 |
error "Invalid entry: #{entry}" |
| 1628 |
end |
| 1629 |
end |
| 1630 |
dependencies |
| 1631 |
end |
| 1632 |
|
| 1633 |
end |
| 1634 |
|
| 1635 |
class Stack < Thor |
| 1636 |
|
| 1637 |
group 'core' |
| 1638 |
|
| 1639 |
# The Stack tasks will install dependencies based on known sets of gems, |
| 1640 |
# regardless of actual application dependency settings. |
| 1641 |
|
| 1642 |
DM_STACK = %w[ |
| 1643 |
extlib |
| 1644 |
data_objects |
| 1645 |
dm-core |
| 1646 |
dm-aggregates |
| 1647 |
dm-migrations |
| 1648 |
dm-timestamps |
| 1649 |
dm-types |
| 1650 |
dm-validations |
| 1651 |
merb_datamapper |
| 1652 |
] |
| 1653 |
|
| 1654 |
MERB_STACK = %w[ |
| 1655 |
extlib |
| 1656 |
merb-core |
| 1657 |
merb-action-args |
| 1658 |
merb-assets |
| 1659 |
merb-cache |
| 1660 |
merb-helpers |
| 1661 |
merb-mailer |
| 1662 |
merb-slices |
| 1663 |
merb-auth |
| 1664 |
merb-auth-core |
| 1665 |
merb-auth-more |
| 1666 |
merb-auth-slice-password |
| 1667 |
merb-param-protection |
| 1668 |
merb-exceptions |
| 1669 |
] + DM_STACK |
| 1670 |
|
| 1671 |
MERB_BASICS = %w[ |
| 1672 |
extlib |
| 1673 |
merb-core |
| 1674 |
merb-action-args |
| 1675 |
merb-assets |
| 1676 |
merb-cache |
| 1677 |
merb-helpers |
| 1678 |
merb-mailer |
| 1679 |
merb-slices |
| 1680 |
] |
| 1681 |
|
| 1682 |
# The following sets are meant for repository lookup; unlike the sets above |
| 1683 |
# these correspond to specific git repository items. |
| 1684 |
|
| 1685 |
MERB_MORE = %w[ |
| 1686 |
merb-action-args |
| 1687 |
merb-assets |
| 1688 |
merb-auth |
| 1689 |
merb-auth-core |
| 1690 |
merb-auth-more |
| 1691 |
merb-auth-slice-password |
| 1692 |
merb-cache |
| 1693 |
merb-exceptions |
| 1694 |
merb-gen |
| 1695 |
merb-haml |
| 1696 |
merb-helpers |
| 1697 |
merb-mailer |
| 1698 |
merb-param-protection |
| 1699 |
merb-slices |
| 1700 |
merb_datamapper |
| 1701 |
] |
| 1702 |
|
| 1703 |
MERB_PLUGINS = %w[ |
| 1704 |
merb_activerecord |
| 1705 |
merb_builder |
| 1706 |
merb_jquery |
| 1707 |
merb_laszlo |
| 1708 |
merb_parts |
| 1709 |
merb_screw_unit |
| 1710 |
merb_sequel |
| 1711 |
merb_stories |
| 1712 |
merb_test_unit |
| 1713 |
] |
| 1714 |
|
| 1715 |
DM_MORE = %w[ |
| 1716 |
dm-adjust |
| 1717 |
dm-aggregates |
| 1718 |
dm-ar-finders |
| 1719 |
dm-cli |
| 1720 |
dm-constraints |
| 1721 |
dm-is-example |
| 1722 |
dm-is-list |
| 1723 |
dm-is-nested_set |
| 1724 |
dm-is-remixable |
| 1725 |
dm-is-searchable |
| 1726 |
dm-is-state_machine |
| 1727 |
dm-is-tree |
| 1728 |
dm-is-versioned |
| 1729 |
dm-migrations |
| 1730 |
dm-observer |
| 1731 |
dm-querizer |
| 1732 |
dm-serializer |
| 1733 |
dm-shorthand |
| 1734 |
dm-sweatshop |
| 1735 |
dm-tags |
| 1736 |
dm-timestamps |
| 1737 |
dm-types |
| 1738 |
dm-validations |
| 1739 |
|
| 1740 |
dm-couchdb-adapter |
| 1741 |
dm-ferret-adapter |
| 1742 |
dm-rest-adapter |
| 1743 |
] |
| 1744 |
|
| 1745 |
DATA_OBJECTS = %w[ |
| 1746 |
data_objects |
| 1747 |
do_derby do_hsqldb |
| 1748 |
do_jdbc |
| 1749 |
do_mysql |
| 1750 |
do_postgres |
| 1751 |
do_sqlite3 |
| 1752 |
] |
| 1753 |
|
| 1754 |
attr_accessor :system, :local, :missing |
| 1755 |
|
| 1756 |
include MerbThorHelper |
| 1757 |
|
| 1758 |
global_method_options = { |
| 1759 |
"--merb-root" => :optional, # the directory to operate on |
| 1760 |
"--ignore-dependencies" => :boolean, # skip sub-dependencies |
| 1761 |
"--version" => :optional # gather specific version of framework |
| 1762 |
} |
| 1763 |
|
| 1764 |
method_options global_method_options |
| 1765 |
def initialize(*args); super; end |
| 1766 |
|
| 1767 |
# List components and their dependencies. |
| 1768 |
# |
| 1769 |
# Examples: |
| 1770 |
# |
| 1771 |
# merb:stack:list # list all standard stack components |
| 1772 |
# merb:stack:list all # list all component sets |
| 1773 |
# merb:stack:list merb-more # list all dependencies of merb-more |
| 1774 |
|
| 1775 |
desc 'list [all|comp]', 'List available components (optionally filtered, defaults to merb stack)' |
| 1776 |
def list(comp = 'stack') |
| 1777 |
if comp == 'all' |
| 1778 |
Merb::Stack.component_sets.keys.sort.each do |comp| |
| 1779 |
unless (components = Merb::Stack.component_sets[comp]).empty? |
| 1780 |
message "Dependencies for '#{comp}' set:" |
| 1781 |
components.each { |c| puts "- #{c}" } |
| 1782 |
end |
| 1783 |
end |
| 1784 |
else |
| 1785 |
message "Dependencies for '#{comp}' set:" |
| 1786 |
Merb::Stack.components(comp).each { |c| puts "- #{c}" } |
| 1787 |
end |
| 1788 |
end |
| 1789 |
|
| 1790 |
# Install stack components or individual gems - from stable rubygems by default. |
| 1791 |
# |
| 1792 |
# See also: Merb::Dependencies#install and Merb::Dependencies#install_dependencies |
| 1793 |
# |
| 1794 |
# Examples: |
| 1795 |
# |
| 1796 |
# merb:stack:install # install the default merb stack |
| 1797 |
# merb:stack:install basics # install a basic set of dependencies |
| 1798 |
# merb:stack:install merb-core # install merb-core from stable |
| 1799 |
# merb:stack:install merb-more --edge # install merb-core from edge |
| 1800 |
# merb:stack:install merb-core thor merb-slices # install the specified gems |
| 1801 |
|
| 1802 |
desc 'install [COMP, ...]', 'Install stack components' |
| 1803 |
method_options "--edge" => :boolean, |
| 1804 |
"--sources" => :optional, |
| 1805 |
"--force" => :boolean, |
| 1806 |
"--dry-run" => :boolean, |
| 1807 |
"--strategy" => :optional |
| 1808 |
def install(*comps) |
| 1809 |
use_edge_gem_server if options[:edge] |
| 1810 |
mngr = self.dependency_manager |
| 1811 |
deps = gather_dependencies(comps) |
| 1812 |
mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1813 |
mngr.install_dependencies(strategy, deps) |
| 1814 |
end |
| 1815 |
|
| 1816 |
# Uninstall stack components or individual gems. |
| 1817 |
# |
| 1818 |
# See also: Merb::Dependencies#uninstall |
| 1819 |
# |
| 1820 |
# Examples: |
| 1821 |
# |
| 1822 |
# merb:stack:uninstall # uninstall the default merb stack |
| 1823 |
# merb:stack:uninstall merb-more # uninstall merb-more |
| 1824 |
# merb:stack:uninstall merb-core thor merb-slices # uninstall the specified gems |
| 1825 |
|
| 1826 |
desc 'uninstall [COMP, ...]', 'Uninstall stack components' |
| 1827 |
method_options "--dry-run" => :boolean, "--force" => :boolean |
| 1828 |
def uninstall(*comps) |
| 1829 |
deps = gather_dependencies(comps) |
| 1830 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1831 |
# Clobber existing local dependencies - based on self.local |
| 1832 |
clobber_dependencies! |
| 1833 |
end |
| 1834 |
|
| 1835 |
# Install or uninstall minigems from the system. |
| 1836 |
# |
| 1837 |
# Due to the specific nature of MiniGems it can only be installed system-wide. |
| 1838 |
# |
| 1839 |
# Examples: |
| 1840 |
# |
| 1841 |
# merb:stack:minigems install # install minigems |
| 1842 |
# merb:stack:minigems uninstall # uninstall minigems |
| 1843 |
|
| 1844 |
desc 'minigems (install|uninstall)', 'Install or uninstall minigems (needs sudo privileges)' |
| 1845 |
def minigems(action) |
| 1846 |
case action |
| 1847 |
when 'install' |
| 1848 |
Kernel.system "#{sudo} thor merb:stack:install_minigems" |
| 1849 |
when 'uninstall' |
| 1850 |
Kernel.system "#{sudo} thor merb:stack:uninstall_minigems" |
| 1851 |
else |
| 1852 |
error "Invalid command: merb:stack:minigems #{action}" |
| 1853 |
end |
| 1854 |
end |
| 1855 |
|
| 1856 |
# hidden minigems install task |
| 1857 |
def install_minigems |
| 1858 |
message "Installing MiniGems" |
| 1859 |
mngr = self.dependency_manager |
| 1860 |
deps = gather_dependencies('minigems') |
| 1861 |
mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1862 |
mngr.force_gem_dir = ::Gem.dir |
| 1863 |
mngr.install_dependencies(strategy, deps) |
| 1864 |
Kernel.system "#{sudo} minigem install" |
| 1865 |
end |
| 1866 |
|
| 1867 |
# hidden minigems uninstall task |
| 1868 |
def uninstall_minigems |
| 1869 |
message "Uninstalling MiniGems" |
| 1870 |
Kernel.system "#{sudo} minigem uninstall" |
| 1871 |
deps = gather_dependencies('minigems') |
| 1872 |
self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir) |
| 1873 |
# Clobber existing local dependencies - based on self.local |
| 1874 |
clobber_dependencies! |
| 1875 |
end |
| 1876 |
|
| 1877 |
protected |
| 1878 |
|
| 1879 |
def gather_dependencies(comps = []) |
| 1880 |
if comps.empty? |
| 1881 |
gems = MERB_STACK |
| 1882 |
else |
| 1883 |
gems = comps.map { |c| Merb::Stack.components(c) }.flatten |
| 1884 |
end |
| 1885 |
|
| 1886 |
version_req = if options[:version] |
| 1887 |
::Gem::Requirement.create(options[:version]) |
| 1888 |
end |
| 1889 |
|
| 1890 |
framework_components = Merb::Stack.framework_components |
| 1891 |
|
| 1892 |
gems.map do |gem| |
| 1893 |
if version_req && framework_components.include?(gem) |
| 1894 |
::Gem::Dependency.new(gem, version_req) |
| 1895 |
else |
| 1896 |
::Gem::Dependency.new(gem, ::Gem::Requirement.default) |
| 1897 |
end |
| 1898 |
end |
| 1899 |
end |
| 1900 |
|
| 1901 |
def strategy |
| 1902 |
options[:strategy] || (options[:edge] ? 'edge' : 'stable') |
| 1903 |
end |
| 1904 |
|
| 1905 |
def dependency_manager |
| 1906 |
@_dependency_manager ||= begin |
| 1907 |
instance = Merb::Dependencies.new |
| 1908 |
instance.options = options |
| 1909 |
instance |
| 1910 |
end |
| 1911 |
end |
| 1912 |
|
| 1913 |
public |
| 1914 |
|
| 1915 |
def self.repository_sets |
| 1916 |
@_repository_sets ||= begin |
| 1917 |
# the component itself as a fallback |
| 1918 |
comps = Hash.new { |(hsh,c)| [c] } |
| 1919 |
|
| 1920 |
# git repository based component sets |
| 1921 |
comps["merb"] = ["merb-core"] + MERB_MORE |
| 1922 |
comps["merb-more"] = MERB_MORE.sort |
| 1923 |
comps["merb-plugins"] = MERB_PLUGINS.sort |
| 1924 |
comps["dm-more"] = DM_MORE.sort |
| 1925 |
comps["do"] = DATA_OBJECTS.sort |
| 1926 |
|
| 1927 |
comps |
| 1928 |
end |
| 1929 |
end |
| 1930 |
|
| 1931 |
def self.component_sets |
| 1932 |
@_component_sets ||= begin |
| 1933 |
# the component itself as a fallback |
| 1934 |
comps = Hash.new { |(hsh,c)| [c] } |
| 1935 |
comps.update(repository_sets) |
| 1936 |
|
| 1937 |
# specific set of dependencies |
| 1938 |
comps["stack"] = MERB_STACK.sort |
| 1939 |
comps["basics"] = MERB_BASICS.sort |
| 1940 |
|
| 1941 |
# orm dependencies |
| 1942 |
comps["datamapper"] = DM_STACK.sort |
| 1943 |
comps["sequel"] = ["merb_sequel", "sequel"] |
| 1944 |
comps["activerecord"] = ["merb_activerecord", "activerecord"] |
| 1945 |
|
| 1946 |
comps |
| 1947 |
end |
| 1948 |
end |
| 1949 |
|
| 1950 |
def self.framework_components |
| 1951 |
%w[merb-core merb-more].inject([]) do |all, comp| |
| 1952 |
all + components(comp) |
| 1953 |
end |
| 1954 |
end |
| 1955 |
|
| 1956 |
def self.components(comp = nil) |
| 1957 |
if comp |
| 1958 |
component_sets[comp] |
| 1959 |
else |
| 1960 |
comps = %w[merb-core merb-more merb-plugins dm-core dm-more] |
| 1961 |
comps.inject([]) do |all, grp| |
| 1962 |
all + (component_sets[grp] || []) |
| 1963 |
end |
| 1964 |
end |
| 1965 |
end |
| 1966 |
|
| 1967 |
def self.select_component_dependencies(dependencies, comp = nil) |
| 1968 |
comps = components(comp) || [] |
| 1969 |
dependencies.select { |dep| comps.include?(dep.name) } |
| 1970 |
end |
| 1971 |
|
| 1972 |
def self.base_components |
| 1973 |
%w[thor rake extlib] |
| 1974 |
end |
| 1975 |
|
| 1976 |
def self.all_components |
| 1977 |
base_components + framework_components |
| 1978 |
end |
| 1979 |
|
| 1980 |
# Find the latest merb-core and gather its dependencies. |
| 1981 |
# We check for 0.9.8 as a minimum release version. |
| 1982 |
def self.core_dependencies(gem_dir = nil, ignore_deps = false) |
| 1983 |
@_core_dependencies ||= begin |
| 1984 |
if gem_dir # add local gems to index |
| 1985 |
orig_gem_path = ::Gem.path |
| 1986 |
::Gem.clear_paths; ::Gem.path.unshift(gem_dir) |
| 1987 |
end |
| 1988 |
deps = [] |
| 1989 |
merb_core = ::Gem::Dependency.new('merb-core', '>= 0.9.8') |
| 1990 |
if gemspec = ::Gem.source_index.search(merb_core).last |
| 1991 |
deps << ::Gem::Dependency.new('merb-core', gemspec.version) |
| 1992 |
if ignore_deps |
| 1993 |
deps += gemspec.dependencies.select do |d| |
| 1994 |
base_components.include?(d.name) |
| 1995 |
end |
| 1996 |
else |
| 1997 |
deps += gemspec.dependencies |
| 1998 |
end |
| 1999 |
end |
| 2000 |
::Gem.path.replace(orig_gem_path) if gem_dir # reset |
| 2001 |
deps |
| 2002 |
end |
| 2003 |
end |
| 2004 |
|
| 2005 |
def self.lookup_repository_name(item) |
| 2006 |
set_name = nil |
| 2007 |
# The merb repo contains -more as well, so it needs special attention |
| 2008 |
return 'merb' if self.repository_sets['merb'].include?(item) |
| 2009 |
|
| 2010 |
# Proceed with finding the item in a known component set |
| 2011 |
self.repository_sets.find do |set, items| |
| 2012 |
next if set == 'merb' |
| 2013 |
items.include?(item) ? (set_name = set) : nil |
| 2014 |
end |
| 2015 |
set_name |
| 2016 |
end |
| 2017 |
|
| 2018 |
end |
| 2019 |
|
| 2020 |
end |