823 lines
28 KiB
Plaintext
823 lines
28 KiB
Plaintext
|
#!/usr/bin/env ruby
|
||
|
require 'rubygems'
|
||
|
require 'rubygems/dependency_installer'
|
||
|
require 'rubygems/uninstaller'
|
||
|
require 'thor'
|
||
|
require 'fileutils'
|
||
|
require 'yaml'
|
||
|
|
||
|
module MerbThorHelper
|
||
|
|
||
|
private
|
||
|
|
||
|
# The current working directory, or Merb app root (--merb-root option).
|
||
|
def working_dir
|
||
|
@_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd)
|
||
|
end
|
||
|
|
||
|
# We should have a ./src dir for local and system-wide management.
|
||
|
def source_dir
|
||
|
@_source_dir ||= File.join(working_dir, 'src')
|
||
|
create_if_missing(@_source_dir)
|
||
|
@_source_dir
|
||
|
end
|
||
|
|
||
|
# If a local ./gems dir is found, it means we are in a Merb app.
|
||
|
def application?
|
||
|
gem_dir
|
||
|
end
|
||
|
|
||
|
# If a local ./gems dir is found, return it.
|
||
|
def gem_dir
|
||
|
if File.directory?(dir = File.join(working_dir, 'gems'))
|
||
|
dir
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# If we're in a Merb app, we can have a ./bin directory;
|
||
|
# create it if it's not there.
|
||
|
def bin_dir
|
||
|
@_bin_dir ||= begin
|
||
|
if gem_dir
|
||
|
dir = File.join(working_dir, 'bin')
|
||
|
create_if_missing(dir)
|
||
|
dir
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Helper to create dir unless it exists.
|
||
|
def create_if_missing(path)
|
||
|
FileUtils.mkdir(path) unless File.exists?(path)
|
||
|
end
|
||
|
|
||
|
# Create a modified executable wrapper in the app's ./bin directory.
|
||
|
def ensure_local_bin_for(*gems)
|
||
|
if bin_dir && File.directory?(bin_dir)
|
||
|
gems.each do |gem|
|
||
|
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
|
||
|
spec = Gem::Specification.load(gemspec_path)
|
||
|
spec.executables.each do |exec|
|
||
|
if File.exists?(executable = File.join(gem_dir, 'bin', exec))
|
||
|
local_executable = File.join(bin_dir, exec)
|
||
|
puts "Adding local executable #{local_executable}"
|
||
|
File.open(local_executable, 'w', 0755) do |f|
|
||
|
f.write(executable_wrapper(spec, exec))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def executable_wrapper(spec, bin_file_name)
|
||
|
<<-TEXT
|
||
|
#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}
|
||
|
#
|
||
|
# This file was generated by merb.thor.
|
||
|
#
|
||
|
# The application '#{spec.name}' is installed as part of a gem, and
|
||
|
# this file is here to facilitate running it.
|
||
|
#
|
||
|
|
||
|
require 'rubygems'
|
||
|
|
||
|
if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
|
||
|
$BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
|
||
|
end
|
||
|
|
||
|
version = "#{Gem::Requirement.default}"
|
||
|
|
||
|
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
||
|
version = $1
|
||
|
ARGV.shift
|
||
|
end
|
||
|
|
||
|
gem '#{spec.name}', version
|
||
|
load '#{bin_file_name}'
|
||
|
TEXT
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# TODO
|
||
|
# - a task to figure out an app's dependencies
|
||
|
# - pulling a specific UUID/Tag (gitspec hash) with clone/update
|
||
|
# - a 'deploy' task (in addition to 'redeploy' ?)
|
||
|
# - eventually take a --orm option for the 'merb-stack' type of tasks
|
||
|
|
||
|
class Merb < Thor
|
||
|
|
||
|
class SourcePathMissing < Exception
|
||
|
end
|
||
|
|
||
|
class GemPathMissing < Exception
|
||
|
end
|
||
|
|
||
|
class GemInstallError < Exception
|
||
|
end
|
||
|
|
||
|
class GemUninstallError < Exception
|
||
|
end
|
||
|
|
||
|
# Install a Merb stack from stable RubyForge gems. Optionally install a
|
||
|
# suitable Rack adapter/server when setting --adapter to one of the
|
||
|
# following: mongrel, emongrel, thin or ebb.
|
||
|
|
||
|
desc 'stable', 'Install extlib, merb-core and merb-more from rubygems'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--adapter" => :optional
|
||
|
def stable
|
||
|
adapters = %w[mongrel emongrel thin ebb]
|
||
|
stable = Stable.new
|
||
|
stable.options = options
|
||
|
if stable.core && stable.more
|
||
|
puts "Installed extlib, merb-core and merb-more"
|
||
|
if options[:adapter] && adapters.include?(options[:adapter]) &&
|
||
|
stable.refresh_from_gems(options[:adapter])
|
||
|
puts "Installed #{options[:adapter]}"
|
||
|
elsif options[:adapter]
|
||
|
puts "Please specify one of the following adapters: #{adapters.join(' ')}"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Stable < Thor
|
||
|
|
||
|
# The Stable tasks deal with known -stable- gems; available
|
||
|
# as shortcuts to Merb and DataMapper gems.
|
||
|
#
|
||
|
# These are pulled from rubyforge and installed into into the
|
||
|
# desired gems dir (either system-wide or into the application's
|
||
|
# gems directory).
|
||
|
|
||
|
include MerbThorHelper
|
||
|
|
||
|
# Gets latest gem versions from RubyForge and installs them.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:edge:core
|
||
|
# thor merb:edge:core --merb-root ./path/to/your/app
|
||
|
# thor merb:edge:core --sources ./path/to/sources.yml
|
||
|
|
||
|
desc 'core', 'Install extlib and merb-core from git HEAD'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def core
|
||
|
refresh_from_gems 'extlib', 'merb-core'
|
||
|
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
|
||
|
end
|
||
|
|
||
|
desc 'more', 'Install merb-more from rubygems'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def more
|
||
|
refresh_from_gems 'merb-more'
|
||
|
ensure_local_bin_for('merb-gen')
|
||
|
end
|
||
|
|
||
|
desc 'plugins', 'Install merb-plugins from rubygems'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def plugins
|
||
|
refresh_from_gems 'merb-plugins'
|
||
|
end
|
||
|
|
||
|
desc 'dm_core', 'Install dm-core from rubygems'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def dm_core
|
||
|
refresh_from_gems 'extlib', 'dm-core'
|
||
|
end
|
||
|
|
||
|
desc 'dm_more', 'Install dm-more from rubygems'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def dm_more
|
||
|
refresh_from_gems 'extlib', 'dm-core', 'dm-more'
|
||
|
end
|
||
|
|
||
|
# Pull from RubyForge and install.
|
||
|
def refresh_from_gems(*components)
|
||
|
gems = Gems.new
|
||
|
gems.options = options
|
||
|
components.all? { |name| gems.install(name) }
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Retrieve latest Merb versions from git and optionally install them.
|
||
|
#
|
||
|
# Note: the --sources option takes a path to a YAML file
|
||
|
# with a regular Hash mapping gem names to git urls.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:edge
|
||
|
# thor merb:edge --install
|
||
|
# thor merb:edge --merb-root ./path/to/your/app
|
||
|
# thor merb:edge --sources ./path/to/sources.yml
|
||
|
|
||
|
desc 'edge', 'Install extlib, merb-core and merb-more from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def edge
|
||
|
edge = Edge.new
|
||
|
edge.options = options
|
||
|
edge.core
|
||
|
edge.more
|
||
|
end
|
||
|
|
||
|
class Edge < Thor
|
||
|
|
||
|
# The Edge tasks deal with known gems from the bleeding edge; available
|
||
|
# as shortcuts to Merb and DataMapper gems.
|
||
|
#
|
||
|
# These are pulled from git and optionally installed into into the
|
||
|
# desired gems dir (either system-wide or into the application's
|
||
|
# gems directory).
|
||
|
|
||
|
include MerbThorHelper
|
||
|
|
||
|
# Gets latest gem versions from git - optionally installs them.
|
||
|
#
|
||
|
# Note: the --sources option takes a path to a YAML file
|
||
|
# with a regular Hash mapping gem names to git urls,
|
||
|
# allowing pulling forks of the official repositories.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:edge:core
|
||
|
# thor merb:edge:core --install
|
||
|
# thor merb:edge:core --merb-root ./path/to/your/app
|
||
|
# thor merb:edge:core --sources ./path/to/sources.yml
|
||
|
|
||
|
desc 'core', 'Update extlib and merb-core from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def core
|
||
|
refresh_from_source 'thor', 'extlib', 'merb-core'
|
||
|
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
|
||
|
end
|
||
|
|
||
|
desc 'more', 'Update merb-more from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def more
|
||
|
refresh_from_source 'merb-more'
|
||
|
ensure_local_bin_for('merb-gen')
|
||
|
end
|
||
|
|
||
|
desc 'plugins', 'Update merb-plugins from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def plugins
|
||
|
refresh_from_source 'merb-plugins'
|
||
|
end
|
||
|
|
||
|
desc 'dm_core', 'Update dm-core from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def dm_core
|
||
|
refresh_from_source 'extlib', 'dm-core'
|
||
|
end
|
||
|
|
||
|
desc 'dm_more', 'Update dm-more from git HEAD'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--sources" => :optional,
|
||
|
"--install" => :boolean
|
||
|
def dm_more
|
||
|
refresh_from_source 'extlib', 'dm-core', 'dm-more'
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
# Pull from git and optionally install the resulting gems.
|
||
|
def refresh_from_source(*components)
|
||
|
source = Source.new
|
||
|
source.options = options
|
||
|
components.each do |name|
|
||
|
source.clone(name)
|
||
|
source.install(name) if options[:install]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class Source < Thor
|
||
|
|
||
|
# The Source tasks deal with gem source packages - mainly from github.
|
||
|
# Any directory inside ./src is regarded as a gem that can be packaged
|
||
|
# and installed from there into the desired gems dir (either system-wide
|
||
|
# or into the application's gems directory).
|
||
|
|
||
|
include MerbThorHelper
|
||
|
|
||
|
# Install a particular gem from source.
|
||
|
#
|
||
|
# If a local ./gems dir is found, or --merb-root is given
|
||
|
# the gems will be installed locally into that directory.
|
||
|
#
|
||
|
# Note that this task doesn't retrieve any (new) source from git;
|
||
|
# To update and install you'd execute the following two tasks:
|
||
|
#
|
||
|
# thor merb:source:update merb-core
|
||
|
# thor merb:source:install merb-core
|
||
|
#
|
||
|
# Alternatively, look at merb:edge and merb:edge:* with --install.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:source:install merb-core
|
||
|
# thor merb:source:install merb-more
|
||
|
# thor merb:source:install merb-more/merb-slices
|
||
|
# thor merb:source:install merb-plugins/merb_helpers
|
||
|
# thor merb:source:install merb-core --merb-root ./path/to/your/app
|
||
|
|
||
|
desc 'install GEM_NAME', 'Install a rubygem from (git) source'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def install(name)
|
||
|
puts "Installing #{name}..."
|
||
|
gem_src_dir = File.join(source_dir, name)
|
||
|
opts = {}
|
||
|
opts[:install_dir] = gem_dir if gem_dir
|
||
|
Merb.install_gem_from_src(gem_src_dir, opts)
|
||
|
rescue Merb::SourcePathMissing
|
||
|
puts "Missing rubygem source path: #{gem_src_dir}"
|
||
|
rescue Merb::GemPathMissing
|
||
|
puts "Missing rubygems path: #{gem_dir}"
|
||
|
rescue => e
|
||
|
puts "Failed to install #{name} (#{e.message})"
|
||
|
end
|
||
|
|
||
|
# Clone a git repository into ./src. The repository can be
|
||
|
# a direct git url or a known -named- repository.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:source:clone dm-core
|
||
|
# thor merb:source:clone dm-core --sources ./path/to/sources.yml
|
||
|
# thor merb:source:clone git://github.com/sam/dm-core.git
|
||
|
|
||
|
desc 'clone REPOSITORY', 'Clone a git repository into ./src'
|
||
|
method_options "--sources" => :optional
|
||
|
def clone(repository)
|
||
|
if repository =~ /^git:\/\//
|
||
|
repository_url = repository
|
||
|
elsif url = Merb.repos(options[:sources])[repository]
|
||
|
repository_url = url
|
||
|
end
|
||
|
|
||
|
if repository_url
|
||
|
repository_name = repository_url[/([\w+|-]+)\.git/u, 1]
|
||
|
fork_name = repository_url[/.com\/+?(.+)\/.+\.git/u, 1]
|
||
|
local_repo_path = "#{source_dir}/#{repository_name}"
|
||
|
|
||
|
if File.directory?(local_repo_path)
|
||
|
puts "\n#{repository_name} repository exists, updating or branching instead of cloning..."
|
||
|
FileUtils.cd(local_repo_path) do
|
||
|
|
||
|
# to avoid conflicts we need to set a remote branch for non official repos
|
||
|
existing_repos = `git remote -v`.split("\n").map{|branch| branch.split(/\s+/)}
|
||
|
origin_repo_url = existing_repos.detect{ |r| r.first == "origin" }.last
|
||
|
|
||
|
# pull from the original repository - no branching needed
|
||
|
if repository_url == origin_repo_url
|
||
|
puts "Pulling from #{repository_url}"
|
||
|
system %{
|
||
|
git fetch
|
||
|
git checkout master
|
||
|
git rebase origin/master
|
||
|
}
|
||
|
# update and switch to a branch for a particular github fork
|
||
|
elsif existing_repos.map{ |r| r.last }.include?(repository_url)
|
||
|
puts "Switching to remote branch: #{fork_name}"
|
||
|
`git checkout -b #{fork_name} #{fork_name}/master`
|
||
|
`git rebase #{fork_name}/master`
|
||
|
# create a new remote branch for a particular github fork
|
||
|
else
|
||
|
puts "Add a new remote branch: #{fork_name}"
|
||
|
`git remote add -f #{fork_name} #{repository_url}`
|
||
|
`git checkout -b#{fork_name} #{fork_name}/master`
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
FileUtils.cd(source_dir) do
|
||
|
puts "\nCloning #{repository_name} repository from #{repository_url}..."
|
||
|
system("git clone --depth=1 #{repository_url} ")
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
puts "No valid repository url given"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Update a specific gem source directory from git. See #clone.
|
||
|
|
||
|
desc 'update REPOSITORY_URL', 'Update a git repository in ./src'
|
||
|
alias :update :clone
|
||
|
|
||
|
# Update all gem sources from git - based on the current branch.
|
||
|
|
||
|
desc 'refresh', 'Pull fresh copies of all source gems'
|
||
|
def refresh
|
||
|
repos = Dir["#{source_dir}/*"]
|
||
|
repos.each do |repo|
|
||
|
next unless File.directory?(repo) && File.exists?(File.join(repo, '.git'))
|
||
|
FileUtils.cd(repo) do
|
||
|
puts "Refreshing #{File.basename(repo)}"
|
||
|
branch = `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1) /'`[/\* (.+)/, 1]
|
||
|
system %{git rebase #{branch}}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class Gems < Thor
|
||
|
|
||
|
# The Gems tasks deal directly with rubygems, either through remotely
|
||
|
# available sources (rubyforge for example) or by searching the
|
||
|
# system-wide gem cache for matching gems. The gems are installed from
|
||
|
# there into the desired gems dir (either system-wide or into the
|
||
|
# application's gems directory).
|
||
|
|
||
|
include MerbThorHelper
|
||
|
|
||
|
# Install a gem and its dependencies.
|
||
|
#
|
||
|
# If a local ./gems dir is found, or --merb-root is given
|
||
|
# the gems will be installed locally into that directory.
|
||
|
#
|
||
|
# The option --cache will look in the system's gem cache
|
||
|
# for the latest version and install it in the apps' gems.
|
||
|
# This is particularly handy for gems that aren't available
|
||
|
# through rubyforge.org - like in-house merb slices etc.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:gems:install merb-core
|
||
|
# thor merb:gems:install merb-core --cache
|
||
|
# thor merb:gems:install merb-core --version 0.9.7
|
||
|
# thor merb:gems:install merb-core --merb-root ./path/to/your/app
|
||
|
|
||
|
desc 'install GEM_NAME', 'Install a gem from rubygems'
|
||
|
method_options "--version" => :optional,
|
||
|
"--merb-root" => :optional,
|
||
|
"--cache" => :boolean
|
||
|
def install(name)
|
||
|
puts "Installing #{name}..."
|
||
|
opts = {}
|
||
|
opts[:version] = options[:version]
|
||
|
opts[:cache] = options[:cache] if gem_dir
|
||
|
opts[:install_dir] = gem_dir if gem_dir
|
||
|
Merb.install_gem(name, opts)
|
||
|
rescue => e
|
||
|
puts "Failed to install #{name} (#{e.message})"
|
||
|
end
|
||
|
|
||
|
# Update a gem and its dependencies.
|
||
|
#
|
||
|
# If a local ./gems dir is found, or --merb-root is given
|
||
|
# the gems will be installed locally into that directory.
|
||
|
#
|
||
|
# The option --cache will look in the system's gem cache
|
||
|
# for the latest version and install it in the apps' gems.
|
||
|
# This is particularly handy for gems that aren't available
|
||
|
# through rubyforge.org - like in-house merb slices etc.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:gems:update merb-core
|
||
|
# thor merb:gems:update merb-core --cache
|
||
|
# thor merb:gems:update merb-core --merb-root ./path/to/your/app
|
||
|
|
||
|
desc 'update GEM_NAME', 'Update a gem from rubygems'
|
||
|
method_options "--merb-root" => :optional,
|
||
|
"--cache" => :boolean
|
||
|
def update(name)
|
||
|
puts "Updating #{name}..."
|
||
|
opts = {}
|
||
|
if gem_dir
|
||
|
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{name}-*.gemspec")].last
|
||
|
gemspec = Gem::Specification.load(gemspec_path)
|
||
|
opts[:version] = Gem::Requirement.new [">#{gemspec.version}"]
|
||
|
end
|
||
|
opts[:install_dir] = gem_dir
|
||
|
opts[:cache] = options[:cache]
|
||
|
end
|
||
|
Merb.install_gem(name, opts)
|
||
|
rescue => e
|
||
|
puts "Failed to update #{name} (#{e.message})"
|
||
|
end
|
||
|
|
||
|
# Uninstall a gem - ignores dependencies.
|
||
|
#
|
||
|
# If a local ./gems dir is found, or --merb-root is given
|
||
|
# the gems will be uninstalled locally from that directory.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:gems:uninstall merb-core
|
||
|
# thor merb:gems:uninstall merb-core --all
|
||
|
# thor merb:gems:uninstall merb-core --version 0.9.7
|
||
|
# thor merb:gems:uninstall merb-core --merb-root ./path/to/your/app
|
||
|
|
||
|
desc 'install GEM_NAME', 'Install a gem from rubygems'
|
||
|
desc 'uninstall GEM_NAME', 'Uninstall a gem'
|
||
|
method_options "--version" => :optional,
|
||
|
"--merb-root" => :optional,
|
||
|
"--all" => :boolean
|
||
|
def uninstall(name)
|
||
|
puts "Uninstalling #{name}..."
|
||
|
opts = {}
|
||
|
opts[:ignore] = true
|
||
|
opts[:all] = options[:all]
|
||
|
opts[:executables] = true
|
||
|
opts[:version] = options[:version]
|
||
|
opts[:install_dir] = gem_dir if gem_dir
|
||
|
Merb.uninstall_gem(name, opts)
|
||
|
rescue => e
|
||
|
puts "Failed to uninstall #{name} (#{e.message})"
|
||
|
end
|
||
|
|
||
|
# Completely remove a gem and all its versions - ignores dependencies.
|
||
|
#
|
||
|
# If a local ./gems dir is found, or --merb-root is given
|
||
|
# the gems will be uninstalled locally from that directory.
|
||
|
#
|
||
|
# Examples:
|
||
|
#
|
||
|
# thor merb:gems:wipe merb-core
|
||
|
# thor merb:gems:wipe merb-core --merb-root ./path/to/your/app
|
||
|
|
||
|
desc 'wipe GEM_NAME', 'Remove a gem completely'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def wipe(name)
|
||
|
puts "Wiping #{name}..."
|
||
|
opts = {}
|
||
|
opts[:ignore] = true
|
||
|
opts[:all] = true
|
||
|
opts[:executables] = true
|
||
|
opts[:install_dir] = gem_dir if gem_dir
|
||
|
Merb.uninstall_gem(name, opts)
|
||
|
rescue => e
|
||
|
puts "Failed to wipe #{name} (#{e.message})"
|
||
|
end
|
||
|
|
||
|
# This task should be executed as part of a deployment setup, where
|
||
|
# the deployment system runs this after the app has been installed.
|
||
|
# Usually triggered by Capistrano, God...
|
||
|
#
|
||
|
# It will regenerate gems from the bundled gems cache for any gem
|
||
|
# that has C extensions - which need to be recompiled for the target
|
||
|
# deployment platform.
|
||
|
|
||
|
desc 'redeploy', 'Recreate any binary gems on the target deployment platform'
|
||
|
def redeploy
|
||
|
require 'tempfile' # for
|
||
|
if File.directory?(specs_dir = File.join(gem_dir, 'specifications')) &&
|
||
|
File.directory?(cache_dir = File.join(gem_dir, 'cache'))
|
||
|
Dir[File.join(specs_dir, '*.gemspec')].each do |gemspec_path|
|
||
|
unless (gemspec = Gem::Specification.load(gemspec_path)).extensions.empty?
|
||
|
if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
|
||
|
gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
|
||
|
# Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
|
||
|
# will complain about copying identical files (same source/destination).
|
||
|
FileUtils.cp(gem_file, gem_file_copy)
|
||
|
Merb.install_gem(gem_file_copy, :install_dir => gem_dir)
|
||
|
File.delete(gem_file_copy)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
puts "No application local gems directory found"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class << self
|
||
|
|
||
|
# Default Git repositories - pass source_config option
|
||
|
# to load a yaml configuration file.
|
||
|
def repos(source_config = nil)
|
||
|
@_repos ||= begin
|
||
|
repositories = {
|
||
|
'merb-core' => "git://github.com/wycats/merb-core.git",
|
||
|
'merb-more' => "git://github.com/wycats/merb-more.git",
|
||
|
'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
|
||
|
'extlib' => "git://github.com/sam/extlib.git",
|
||
|
'dm-core' => "git://github.com/sam/dm-core.git",
|
||
|
'dm-more' => "git://github.com/sam/dm-more.git",
|
||
|
'thor' => "git://github.com/wycats/thor.git"
|
||
|
}
|
||
|
end
|
||
|
if source_config && File.exists?(source_config)
|
||
|
@_repos.merge(YAML.load(File.read(source_config)))
|
||
|
else
|
||
|
@_repos
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Install a gem - looks remotely and local gem cache;
|
||
|
# won't process rdoc or ri options.
|
||
|
def install_gem(gem, options = {})
|
||
|
from_cache = (options.key?(:cache) && options.delete(:cache))
|
||
|
if from_cache
|
||
|
install_gem_from_cache(gem, options)
|
||
|
else
|
||
|
version = options.delete(:version)
|
||
|
Gem.configuration.update_sources = false
|
||
|
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
||
|
exception = nil
|
||
|
begin
|
||
|
installer.install gem, version
|
||
|
rescue Gem::InstallError => e
|
||
|
exception = e
|
||
|
rescue Gem::GemNotFoundException => e
|
||
|
if from_cache && gem_file = find_gem_in_cache(gem, version)
|
||
|
puts "Located #{gem} in gem cache..."
|
||
|
installer.install gem_file
|
||
|
else
|
||
|
exception = e
|
||
|
end
|
||
|
rescue => e
|
||
|
exception = e
|
||
|
end
|
||
|
if installer.installed_gems.empty? && exception
|
||
|
puts "Failed to install gem '#{gem}' (#{exception.message})"
|
||
|
end
|
||
|
installer.installed_gems.each do |spec|
|
||
|
puts "Successfully installed #{spec.full_name}"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Install a gem - looks in the system's gem cache instead of remotely;
|
||
|
# won't process rdoc or ri options.
|
||
|
def install_gem_from_cache(gem, options = {})
|
||
|
version = options.delete(:version)
|
||
|
Gem.configuration.update_sources = false
|
||
|
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
||
|
exception = nil
|
||
|
begin
|
||
|
if gem_file = find_gem_in_cache(gem, version)
|
||
|
puts "Located #{gem} in gem cache..."
|
||
|
installer.install gem_file
|
||
|
else
|
||
|
raise Gem::InstallError, "Unknown gem #{gem}"
|
||
|
end
|
||
|
rescue Gem::InstallError => e
|
||
|
exception = e
|
||
|
end
|
||
|
if installer.installed_gems.empty? && exception
|
||
|
puts "Failed to install gem '#{gem}' (#{e.message})"
|
||
|
end
|
||
|
installer.installed_gems.each do |spec|
|
||
|
puts "Successfully installed #{spec.full_name}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Install a gem from source - builds and packages it first then installs it.
|
||
|
def install_gem_from_src(gem_src_dir, options = {})
|
||
|
raise SourcePathMissing unless File.directory?(gem_src_dir)
|
||
|
raise GemPathMissing if options[:install_dir] && !File.directory?(options[:install_dir])
|
||
|
|
||
|
gem_name = File.basename(gem_src_dir)
|
||
|
gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
|
||
|
|
||
|
# We need to use local bin executables if available.
|
||
|
thor = which('thor')
|
||
|
rake = which('rake')
|
||
|
|
||
|
# Handle pure Thor installation instead of Rake
|
||
|
if File.exists?(File.join(gem_src_dir, 'Thorfile'))
|
||
|
# Remove any existing packages.
|
||
|
FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
|
||
|
# Create the package.
|
||
|
FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
|
||
|
# Install the package using rubygems.
|
||
|
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
||
|
FileUtils.cd(File.dirname(package)) do
|
||
|
install_gem(File.basename(package), options.dup)
|
||
|
return
|
||
|
end
|
||
|
else
|
||
|
raise Merb::GemInstallError, "No package found for #{gem_name}"
|
||
|
end
|
||
|
# Handle standard installation through Rake
|
||
|
else
|
||
|
# Clean and regenerate any subgems for meta gems.
|
||
|
Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
|
||
|
FileUtils.cd(File.dirname(rakefile)) { system("#{rake} clobber_package; #{rake} package") }
|
||
|
end
|
||
|
|
||
|
# Handle the main gem install.
|
||
|
if File.exists?(File.join(gem_src_dir, 'Rakefile'))
|
||
|
# Remove any existing packages.
|
||
|
FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
|
||
|
# Create the main gem pkg dir if it doesn't exist.
|
||
|
FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
|
||
|
# Copy any subgems to the main gem pkg dir.
|
||
|
Dir[File.join(gem_src_dir, '**', 'pkg', '*.gem')].each do |subgem_pkg|
|
||
|
FileUtils.cp(subgem_pkg, gem_pkg_dir)
|
||
|
end
|
||
|
|
||
|
# Finally generate the main package and install it; subgems
|
||
|
# (dependencies) are local to the main package.
|
||
|
FileUtils.cd(gem_src_dir) do
|
||
|
system("#{rake} package")
|
||
|
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
||
|
FileUtils.cd(File.dirname(package)) do
|
||
|
install_gem(File.basename(package), options.dup)
|
||
|
return
|
||
|
end
|
||
|
else
|
||
|
raise Merb::GemInstallError, "No package found for #{gem_name}"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
raise Merb::GemInstallError, "No Rakefile found for #{gem_name}"
|
||
|
end
|
||
|
|
||
|
# Uninstall a gem.
|
||
|
def uninstall_gem(gem, options = {})
|
||
|
if options[:version] && !options[:version].is_a?(Gem::Requirement)
|
||
|
options[:version] = Gem::Requirement.new ["= #{version}"]
|
||
|
end
|
||
|
begin
|
||
|
Gem::Uninstaller.new(gem, options).uninstall
|
||
|
rescue => e
|
||
|
raise GemUninstallError, "Failed to uninstall #{gem}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Will prepend sudo on a suitable platform.
|
||
|
def sudo
|
||
|
@_sudo ||= begin
|
||
|
windows = PLATFORM =~ /win32|cygwin/ rescue nil
|
||
|
windows ? "" : "sudo "
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Use the local bin/* executables if available.
|
||
|
def which(executable)
|
||
|
if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
|
||
|
exec
|
||
|
else
|
||
|
executable
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def find_gem_in_cache(gem, version)
|
||
|
spec = if version
|
||
|
version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
|
||
|
Gem.source_index.find_name(gem, version).first
|
||
|
else
|
||
|
Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
|
||
|
end
|
||
|
if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
|
||
|
gem_file
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class Tasks < Thor
|
||
|
|
||
|
include MerbThorHelper
|
||
|
|
||
|
# Install Thor, Rake and RSpec into the local gems dir, by copying it from
|
||
|
# the system-wide rubygems cache - which is OK since we needed it to run
|
||
|
# this task already.
|
||
|
#
|
||
|
# After this we don't need the system-wide rubygems anymore, as all required
|
||
|
# executables are available in the local ./bin directory.
|
||
|
#
|
||
|
# RSpec is needed here because source installs might fail when running
|
||
|
# rake tasks where spec/rake/spectask has been required.
|
||
|
|
||
|
desc 'setup', 'Install Thor, Rake and RSpec in the local gems dir'
|
||
|
method_options "--merb-root" => :optional
|
||
|
def setup
|
||
|
if $0 =~ /^(\.\/)?bin\/thor$/
|
||
|
puts "You cannot run the setup from #{$0} - try #{File.basename($0)} merb:tasks:setup instead"
|
||
|
return
|
||
|
end
|
||
|
create_if_missing(File.join(working_dir, 'gems'))
|
||
|
Merb.install_gem('thor', :cache => true, :install_dir => gem_dir)
|
||
|
Merb.install_gem('rake', :cache => true, :install_dir => gem_dir)
|
||
|
Merb.install_gem('rspec', :cache => true, :install_dir => gem_dir)
|
||
|
ensure_local_bin_for('thor', 'rake', 'rspec')
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|