lib/buildr/core/application.rb in vic-buildr-1.3.1 vs lib/buildr/core/application.rb in vic-buildr-1.3.3
- old
+ new
@@ -33,27 +33,28 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-require 'benchmark'
+require 'highline/import'
require 'rake'
require 'rubygems/source_info_cache'
require 'buildr/core/application_cli'
require 'buildr/core/util'
# Gem::user_home is nice, but ENV['HOME'] lets you override from the environment.
ENV["HOME"] ||= File.expand_path(Gem::user_home)
ENV['BUILDR_ENV'] ||= 'development'
+
module Buildr
# Provide settings that come from three sources.
#
# User settings are placed in the .buildr/settings.yaml file located in the user's home directory.
- # The should only be used for settings that are specific to the user and applied the same way
+ # They should only be used for settings that are specific to the user and applied the same way
# across all builds. Example for user settings are preferred repositories, path to local repository,
# user/name password for uploading to remote repository.
#
# Build settings are placed in the build.yaml file located in the build directory. They help keep
# the buildfile and build.yaml file simple and readable, working to the advantages of each one.
@@ -90,15 +91,16 @@
end
private
def load_from(base_name, dir = nil)
- file_name = ['yaml', 'yml'].map { |ext| File.expand_path("#{base_name}.#{ext}", dir) }.find { |fn| File.exist?(fn) }
+ base_name = File.expand_path(base_name, dir) if dir
+ file_name = ['yaml', 'yml'].map { |ext| "#{base_name}.#{ext}" }.find { |fn| File.exist?(fn) }
return {} unless file_name
yaml = YAML.load(File.read(file_name)) || {}
fail "Expecting #{file_name} to be a map (name: value)!" unless Hash === yaml
- @application.build_files << file_name
+ @application.buildfile.enhance [file_name]
yaml
end
end
@@ -118,14 +120,15 @@
@name = 'Buildr'
@requires = []
@top_level_tasks = []
parse_options
collect_tasks
- top_level_tasks.unshift 'buildr:initialize'
@home_dir = File.expand_path('.buildr', ENV['HOME'])
- mkpath @home_dir unless File.exist?(@home_dir)
+ mkpath @home_dir, :verbose=>false unless File.exist?(@home_dir)
@environment = ENV['BUILDR_ENV'] ||= 'development'
+ @on_completion = []
+ @on_failure = []
end
# Returns list of Gems associated with this buildfile, as listed in build.yaml.
# Each entry is of type Gem::Specification.
attr_reader :gems
@@ -136,27 +139,84 @@
# Copied from BUILD_ENV.
attr_reader :environment
# Returns the Settings associated with this build.
def settings
+ fail "Internal error: Called Buildr.settings before buildfile located" unless rakefile
@settings ||= Settings.new(self)
end
# :call-seq:
# buildfile
+ # Returns the buildfile as a task that you can use as a dependency.
def buildfile
- rakefile
+ @buildfile_task ||= BuildfileTask.define_task(File.expand_path(rakefile))
end
+
+ # Files that complement the buildfile itself
+ def build_files #:nodoc:
+ buildfile.prerequisites
+ end
+ def run
+ standard_exception_handling do
+ find_buildfile
+ load_gems
+ load_artifacts
+ load_tasks
+ load_requires
+ load_buildfile
+ load_imports
+ task('buildr:initialize').invoke
+ top_level
+ end
+ title, message = 'Your build has completed', "#{Dir.pwd}\nbuildr #{@top_level_tasks.join(' ')}"
+ @on_completion.each { |block| block.call(title, message) rescue nil }
+ end
+
+ # Yields to block on successful completion. Primarily used for notifications.
+ def on_completion(&block)
+ @on_completion << block
+ end
+
+ # Yields to block on failure with exception. Primarily used for notifications.
+ def on_failure(&block)
+ @on_failure << block
+ end
+
+ # Not for external consumption.
+ def switch_to_namespace(names) #:nodoc:
+ current, @scope = @scope, names
+ begin
+ yield
+ ensure
+ @scope = current
+ end
+ end
+
# :call-seq:
- # build_files => files
+ # deprecated(message)
#
- # Returns a list of build files. These are files used by the build,
- def build_files
- [buildfile].compact + Array(@build_files)
+ # Use with deprecated methods and classes. This method automatically adds the file name and line number,
+ # and the text 'Deprecated' before the message, and eliminated duplicate warnings. It only warns when
+ # running in verbose mode.
+ #
+ # For example:
+ # deprecated 'Please use new_foo instead of foo.'
+ def deprecated(message) #:nodoc:
+ return unless verbose
+ "#{caller[1]}: Deprecated: #{message}".tap do |message|
+ @deprecated ||= {}
+ unless @deprecated[message]
+ @deprecated[message] = true
+ warn message
+ end
+ end
end
+ private
+
# Returns Gem::Specification for every listed and installed Gem, Gem::Dependency
# for listed and uninstalled Gem, which is the installed before loading the buildfile.
def listed_gems #:nodoc:
Array(settings.build['gems']).map do |dep|
name, trail = dep.scan(/^\s*(\S*)\s*(.*)\s*$/).first
@@ -164,32 +224,11 @@
versions = ['>= 0'] if versions.empty?
dep = Gem::Dependency.new(name, versions)
Gem::SourceIndex.from_installed_gems.search(dep).last || dep
end
end
- private :listed_gems
- def run
- times = Benchmark.measure do
- standard_exception_handling do
- find_buildfile
- load_gems
- load_artifacts
- load_tasks
- load_buildfile
- top_level
- end
- end
- if verbose
- real = []
- real << ("%ih" % (times.real / 3600)) if times.real >= 3600
- real << ("%im" % ((times.real / 60) % 60)) if times.real >= 60
- real << ("%.3fs" % (times.real % 60))
- puts "Completed in #{real.join}"
- end
- end
-
# Load artifact specs from the build.yaml file, making them available
# by name ( ruby symbols ).
def load_artifacts #:nodoc:
hash = settings.build['artifacts']
return unless hash
@@ -200,40 +239,31 @@
# Load/install all Gems specified in build.yaml file.
def load_gems #:nodoc:
missing_deps, installed = listed_gems.partition { |gem| gem.is_a?(Gem::Dependency) }
unless missing_deps.empty?
- remote = missing_deps.map { |dep| Gem::SourceInfoCache.search(dep).last || dep }
- not_found_deps, install = remote.partition { |gem| gem.is_a?(Gem::Dependency) }
- fail Gem::LoadError, "Build requires the gems #{not_found_deps.join(', ')}, which cannot be found in local or remote repository." unless not_found_deps.empty?
- uses = "This build requires the gems #{install.map(&:full_name).join(', ')}:"
- fail Gem::LoadError, "#{uses} to install, run Buildr interactively." unless $stdout.isatty
- unless agree("#{uses} do you want me to install them? [Y/n]", true)
- fail Gem::LoadError, 'Cannot build without these gems.'
- end
- install.each do |spec|
- say "Installing #{spec.full_name} ... " if verbose
- Util.ruby 'install', spec.name, '-v', spec.version.to_s, :command => 'gem', :sudo => true, :verbose => false
- Gem.source_index.load_gems_in Gem::SourceIndex.installed_spec_directories
- end
- installed += install
+ newly_installed = Util::Gems.install(*missing_deps)
+ installed += newly_installed
end
-
installed.each do |spec|
if gem(spec.name, spec.version.to_s)
- # FileList[spec.require_paths.map { |path| File.expand_path("#{path}/*.rb", spec.full_gem_path) }].
- # map { |path| File.basename(path) }.each { |file| require file }
- # FileList[File.expand_path('tasks/*.rake', spec.full_gem_path)].each do |file|
- # Buildr.application.add_import file
- # end
+ # TODO: is this intended to load rake tasks from the installed gems?
+ # We should use a convention like .. if the gem has a _buildr.rb file, load it.
+
+ #FileList[spec.require_paths.map { |path| File.expand_path("#{path}/*.rb", spec.full_gem_path) }].
+ # map { |path| File.basename(path) }.each { |file| require file }
+ #FileList[File.expand_path('tasks/*.rake', spec.full_gem_path)].each do |file|
+ # Buildr.application.add_import file
+ #end
end
end
@gems = installed
end
- def find_buildfile
- here = Dir.pwd
+ def find_buildfile #:nodoc:
+ here = original_dir
+ Dir.chdir(here) unless Dir.pwd == here
while ! have_rakefile
Dir.chdir('..')
if Dir.pwd == here || options.nosearch
error = "No Buildfile found (looking for: #{@rakefiles.join(', ')})"
if STDIN.isatty
@@ -245,66 +275,86 @@
end
here = Dir.pwd
end
end
- def load_buildfile
- @requires.each { |name| require name }
- puts "(in #{Dir.pwd}, #{environment})"
+ def load_buildfile #:nodoc:
+ info "(in #{Dir.pwd}, #{environment})"
load File.expand_path(@rakefile) if @rakefile != ''
- load_imports
+ buildfile.enhance @requires.select { |f| File.file?(f) }.map{ |f| File.expand_path(f) }
end
- # Loads buildr.rake files from users home directory and project directory.
+ def load_requires #:nodoc:
+ @requires.each { |name| require name }
+ end
+
+ # Loads buildr.rb files from users home directory and project directory.
# Loads custom tasks from .rake files in tasks directory.
def load_tasks #:nodoc:
- @build_files = [ File.expand_path('buildr.rb', ENV['HOME']), 'buildr.rb' ].select { |file| File.exist?(file) }
- @build_files += [ File.expand_path('buildr.rake', ENV['HOME']), File.expand_path('buildr.rake') ].
+ files = [ File.expand_path('buildr.rb', ENV['HOME']), 'buildr.rb' ].select { |file| File.exist?(file) }
+ files += [ File.expand_path('buildr.rake', ENV['HOME']), File.expand_path('buildr.rake') ].
select { |file| File.exist?(file) }.each { |file| warn "Please use '#{file.ext('rb')}' instead of '#{file}'" }
#Load local tasks that can be used in the Buildfile.
- @build_files += Dir["#{Dir.pwd}/tasks/*.rake"]
- @build_files.each do |file|
+ files += Dir[File.expand_path('tasks/*.rake', original_dir)]
+ files.each do |file|
unless $LOADED_FEATURES.include?(file)
load file
$LOADED_FEATURES << file
end
end
+ buildfile.enhance files
true
end
- private :load_tasks
- # :call-seq:
- # deprecated(message)
- #
- # Use with deprecated methods and classes. This method automatically adds the file name and line number,
- # and the text 'Deprecated' before the message, and eliminated duplicate warnings. It only warns when
- # running in verbose mode.
- #
- # For example:
- # deprecated 'Please use new_foo instead of foo.'
- def deprecated(message) #:nodoc:
- return unless verbose
- "#{caller[1]}: Deprecated: #{message}".tap do |message|
- @deprecated ||= {}
- unless @deprecated[message]
- @deprecated[message] = true
- warn message
+ def display_prerequisites
+ invoke_task('buildr:initialize')
+ tasks.each do |task|
+ if task.name =~ options.show_task_pattern
+ puts "buildr #{task.name}"
+ task.prerequisites.each { |prereq| puts " #{prereq}" }
end
end
end
-
- # Not for external consumption.
- def switch_to_namespace(names) #:nodoc:
- current, @scope = @scope, names
+
+ # Provide standard execption handling for the given block.
+ def standard_exception_handling
begin
yield
- ensure
- @scope = current
+ rescue SystemExit => ex
+ # Exit silently with current status
+ exit(ex.status)
+ rescue SystemExit, GetoptLong::InvalidOption => ex
+ # Exit silently
+ exit(1)
+ rescue Exception => ex
+ title, message = 'Your build failed with an error', "#{Dir.pwd}:\n#{ex.message}"
+ @on_failure.each { |block| block.call(title, message, ex) rescue nil }
+ # Exit with error message
+ $stderr.puts "buildr aborted!"
+ $stderr.puts $terminal.color(ex.message, :red)
+ if options.trace
+ $stderr.puts ex.backtrace.join("\n")
+ else
+ $stderr.puts ex.backtrace.select { |str| str =~ /#{buildfile}/ }.map { |line| $terminal.color(line, :red) }.join("\n")
+ $stderr.puts "(See full trace by running task with --trace)"
+ end
+ exit(1)
end
end
-
+
end
+
+
+ # This task stands for the buildfile and all its associated helper files (e.g., buildr.rb, build.yaml).
+ # By using this task as a prerequisite for other tasks, you can ensure these tasks will be needed
+ # whenever the buildfile changes.
+ class BuildfileTask < Rake::FileTask
+
+ def timestamp
+ ([name] + prerequisites).map { |f| File.stat(f).mtime }.max rescue Time.now
+ end
+ end
class << self
task 'buildr:initialize' do
@@ -335,29 +385,79 @@
Buildr.application = Buildr::Application.new
end
-# Add a touch of colors (red) to warnings.
+# Add a touch of color when available and running in terminal.
if $stdout.isatty
begin
require 'Win32/Console/ANSI' if Config::CONFIG['host_os'] =~ /mswin/
HighLine.use_color = true
rescue LoadError
end
+else
+ HighLine.use_color = false
end
-if HighLine.use_color?
- module Kernel #:nodoc:
- alias :warn_without_color :warn
- def warn(message)
- warn_without_color $terminal.color(message.to_s, :red)
+
+# Let's see if we can use Growl. We do this at the very end, loading Ruby Cocoa
+# could slow the build down, so later is better. We only do this when running
+# from the console in verbose mode.
+if $stdout.isatty && verbose && RUBY_PLATFORM =~ /darwin/
+ begin
+ require 'osx/cocoa'
+ icon = OSX::NSApplication.sharedApplication.applicationIconImage
+ icon = OSX::NSImage.alloc.initWithContentsOfFile(File.join(File.dirname(__FILE__), '../resources/buildr.icns'))
+
+ # Register with Growl, that way you can turn notifications on/off from system preferences.
+ OSX::NSDistributedNotificationCenter.defaultCenter.
+ postNotificationName_object_userInfo_deliverImmediately(:GrowlApplicationRegistrationNotification, nil,
+ { :ApplicationName=>'Buildr', :AllNotifications=>['Completed', 'Failed'],
+ :ApplicationIcon=>icon.TIFFRepresentation }, true)
+
+ notify = lambda do |type, title, message|
+ OSX::NSDistributedNotificationCenter.defaultCenter.
+ postNotificationName_object_userInfo_deliverImmediately(:GrowlNotification, nil,
+ { :ApplicationName=>'Buildr', :NotificationName=>type,
+ :NotificationTitle=>title, :NotificationDescription=>message }, true)
end
+ Buildr.application.on_completion { |title, message| notify['Completed', title, message] }
+ Buildr.application.on_failure { |title, message, ex| notify['Failed', title, message] }
+ rescue Exception # No growl
end
+elsif $stdout.isatty && verbose
+ notify = lambda { |type, title, message| $stdout.puts "[#{type}] #{title}: #{message}" }
+ Buildr.application.on_completion { |title, message| notify['Completed', title, message] }
+ Buildr.application.on_failure { |title, message, ex| notify['Failed', title, message] }
end
+alias :warn_without_color :warn
+
+# Show warning message.
+def warn(message)
+ warn_without_color $terminal.color(message.to_s, :blue) if verbose
+end
+
+# Show error message. Use this when you need to show an error message and not throwing
+# an exception that will stop the build.
+def error(message)
+ puts $terminal.color(message.to_s, :red)
+end
+
+# Show optional information. The message is printed only when running in verbose
+# mode (the default).
+def info(message)
+ puts message if verbose
+end
+
+# Show message. The message is printed out only when running in trace mode.
+def trace(message)
+ puts message if Buildr.application.options.trace
+end
+
+
module Rake #:nodoc
class Task #:nodoc:
def invoke(*args)
task_args = TaskArguments.new(arg_names, args)
invoke_with_call_chain(task_args, Thread.current[:rake_chain] || InvocationChain::EMPTY)
@@ -369,16 +469,21 @@
if application.options.trace
puts "** Invoke #{name} #{format_trace_flags}"
end
return if @already_invoked
@already_invoked = true
- invoke_prerequisites(task_args, new_chain)
begin
+ invoke_prerequisites(task_args, new_chain)
+ rescue
+ trace "Exception while invoking prerequisites of task #{self.inspect}"
+ raise
+ end
+ begin
old_chain, Thread.current[:rake_chain] = Thread.current[:rake_chain], new_chain
execute(task_args) if needed?
ensure
Thread.current[:rake_chain] = nil
end
end
end
end
-end
+end
\ No newline at end of file