lib/buildr/core/application.rb in vic-buildr-1.3.3 vs lib/buildr/core/application.rb in vic-buildr-1.3.4

- old
+ new

@@ -11,10 +11,11 @@ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. + # Portion of this file derived from Rake. # Copyright (c) 2003, 2004 Jim Weirich # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -33,19 +34,19 @@ # 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 'highline/import' require 'rake' +require 'highline/import' require 'rubygems/source_info_cache' -require 'buildr/core/application_cli' require 'buildr/core/util' +Gem.autoload :SourceInfoCache, 'rubygems/source_info_cache' # Gem::user_home is nice, but ENV['HOME'] lets you override from the environment. -ENV["HOME"] ||= File.expand_path(Gem::user_home) +ENV['HOME'] ||= File.expand_path(Gem::user_home) ENV['BUILDR_ENV'] ||= 'development' module Buildr @@ -66,23 +67,26 @@ # The settings for the current environment are obtained by calling #profile. class Settings def initialize(application) #:nodoc: @application = application - @user = load_from('settings', @application.home_dir) - @build = load_from('build') - @profiles = load_from('profiles') end # User settings loaded from setting.yaml file in user's home directory. - attr_reader :user + def user + @user ||= load_from('settings', @application.home_dir) + end # Build settings loaded from build.yaml file in build directory. - attr_reader :build + def build + @build ||= load_from('build') + end # Profiles loaded from profiles.yaml file in build directory. - attr_reader :profiles + def profiles + @profiles ||= load_from('profiles') + end # :call-seq: # profile => hash # # Returns the profile for the current environment. @@ -90,13 +94,16 @@ profiles[@application.environment] ||= {} end private - def load_from(base_name, dir = nil) - base_name = File.expand_path(base_name, dir) if dir - file_name = ['yaml', 'yml'].map { |ext| "#{base_name}.#{ext}" }.find { |fn| File.exist?(fn) } + def load_from(name, path = nil) + unless path + fail "Internal error: attempting to access local setting before buildfile located" unless @application.rakefile + path = File.dirname(@application.rakefile) + end + file_name = ['yaml', 'yml'].map { |ext| File.join(path, "#{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.buildfile.enhance [file_name] yaml @@ -105,96 +112,83 @@ end class Application < Rake::Application #:nodoc: + # Deprecated: rakefile/Rakefile, removed in 1.5 DEFAULT_BUILDFILES = ['buildfile', 'Buildfile'] + DEFAULT_RAKEFILES - include CommandLineInterface - attr_reader :rakefiles, :requires private :rakefiles, :requires def initialize super - @rakefiles = DEFAULT_BUILDFILES - @name = 'Buildr' - @requires = [] + @rakefiles = DEFAULT_BUILDFILES.dup @top_level_tasks = [] - parse_options - collect_tasks @home_dir = File.expand_path('.buildr', ENV['HOME']) mkpath @home_dir, :verbose=>false unless File.exist?(@home_dir) - @environment = ENV['BUILDR_ENV'] ||= 'development' + @settings = Settings.new(self) @on_completion = [] @on_failure = [] end + def run + standard_exception_handling do + init 'Buildr' + load_buildfile + top_level + end + end + + # Not for external consumption. + def switch_to_namespace(names) #:nodoc: + current, @scope = @scope, names + begin + yield + ensure + @scope = current + end + end + # Returns list of Gems associated with this buildfile, as listed in build.yaml. # Each entry is of type Gem::Specification. attr_reader :gems # Buildr home directory, .buildr under user's home directory. attr_reader :home_dir # Copied from BUILD_ENV. - attr_reader :environment + def environment + ENV['BUILDR_ENV'] + end # 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 + attr_reader :settings # :call-seq: # buildfile # Returns the buildfile as a task that you can use as a dependency. def buildfile @buildfile_task ||= BuildfileTask.define_task(File.expand_path(rakefile)) end - + # Files that complement the buildfile itself def build_files #:nodoc: + deprecated 'Please call buildfile.prerequisites instead' 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: # 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 @@ -211,34 +205,210 @@ 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 - versions = trail.scan(/[=><~!]{0,2}\s*[\d\.]+/) - versions = ['>= 0'] if versions.empty? - dep = Gem::Dependency.new(name, versions) - Gem::SourceIndex.from_installed_gems.search(dep).last || dep + protected + + def load_buildfile # replaces load_rakefile + standard_exception_handling do + find_buildfile + load_gems + load_artifact_ns + load_tasks + raw_load_buildfile 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 - raise "Expected 'artifacts' element to be a hash" unless Hash === hash - # Currently we only use one artifact namespace to rule them all. (the root NS) - Buildr::ArtifactNamespace.load(:root => hash) + def top_level # adds on_completion hook + standard_exception_handling do + if options.show_tasks + display_tasks_and_comments + elsif options.show_prereqs + display_prerequisites + elsif options.execute + eval options.execute + else + @start = Time.now + top_level_tasks.each { |task_name| invoke_task(task_name) } + if verbose + elapsed = Time.now - @start + real = [] + real << ('%ih' % (elapsed / 3600)) if elapsed >= 3600 + real << ('%im' % ((elapsed / 60) % 60)) if elapsed >= 60 + real << ('%.3fs' % (elapsed % 60)) + puts $terminal.color("Completed in #{real.join}", :green) + end + # On OS X this will load Cocoa and Growl which takes half a second we + # don't want to measure, so put this after the console message. + title, message = "Your build has completed", "#{Dir.pwd}\nbuildr #{@top_level_tasks.join(' ')}" + @on_completion.each do |block| + block.call(title, message) rescue nil + end + end + end end + + def handle_options + options.rakelib = ['tasks'] + + opts = OptionParser.new + opts.banner = "buildr [-f rakefile] {options} targets..." + opts.separator "" + opts.separator "Options are ..." + opts.on_tail("-h", "--help", "-H", "Display this help message.") do + puts opts + exit + end + + standard_buildr_options.each { |args| opts.on(*args) } + parsed_argv = opts.parse(ARGV) + parsed_argv + end + + def standard_buildr_options # replaces standard_rake_options + [ + ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + options.show_tasks = true + options.full_description = true + options.show_task_pattern = Regexp.new(value || '') + } + ], + ['--execute', '-E CODE', + "Execute some Ruby code after loading the buildfile", + lambda { |value| options.execute = value } + ], + ['--environment', '-e ENV', + "Environment name (e.g. development, test, production).", + lambda { |value| ENV['BUILDR_ENV'] = value } + ], + ['--generate [PATH]', + "Generate buildfile from either pom.xml file or directory path.", + lambda { |value| + value ||= File.exist?('pom.xml') ? 'pom.xml' : Dir.pwd + raw_generate_buildfile value + exit + } + ], + ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ['--prereqs', '-P [PATTERN]', "Display the tasks and dependencies (matching optional PATTERN), then exit.", + lambda { |value| + options.show_prereqs = true + options.show_task_pattern = Regexp.new(value || '') + } + ], + ['--quiet', '-q', "Do not log messages to standard output.", + lambda { |value| verbose(false) } + ], + ['--buildfile', '-f FILE', "Use FILE as the buildfile.", + lambda { |value| + @rakefiles.clear + @rakefiles << value + } + ], + ['--rakelibdir', '--rakelib', '-R PATH', + "Auto-import any .rake files in PATH. (default is 'tasks')", + lambda { |value| options.rakelib = value.split(':') } + ], + ['--require', '-r MODULE', "Require MODULE before executing rakefile.", + lambda { |value| + begin + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError => ex2 + raise ex + end + end + } + ], + ['--rules', "Trace the rules resolution.", + lambda { |value| options.trace_rules = true } + ], + ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.", + lambda { |value| options.nosearch = true } + ], + ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.", + lambda { |value| + verbose(false) + options.silent = true + } + ], + ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.", + lambda { |value| + options.show_tasks = true + options.show_task_pattern = Regexp.new(value || '') + options.full_description = false + } + ], + ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.", + lambda { |value| + options.trace = true + verbose(true) + } + ], + ['--verbose', '-v', "Log message to standard output (default).", + lambda { |value| verbose(true) } + ], + ['--version', '-V', "Display the program version.", + lambda { |value| + puts "Buildr #{Buildr::VERSION} #{RUBY_PLATFORM[/java/] && '(JRuby '+JRUBY_VERSION+')'}" + exit + } + ] + ] + end + + def find_buildfile + buildfile, location = find_rakefile_location || (tty_output? && ask_generate_buildfile) + fail "No Buildfile found (looking for: #{@rakefiles.join(', ')})" if buildfile.nil? + @rakefile = buildfile + Dir.chdir(location) + end + + def ask_generate_buildfile + source = choose do |menu| + menu.header = "To use Buildr you need a buildfile. Do you want me to create one?" + menu.choice("From Maven2 POM file") { 'pom.xml' } if File.exist?('pom.xml') + menu.choice("From directory structure") { Dir.pwd } + menu.choice("Cancel") { } + end + if source + buildfile = raw_generate_buildfile(source) + [buildfile, File.dirname(buildfile)] + end + end + + def raw_generate_buildfile(source) + # We need rakefile to be known, for settings.build to be accessible. + @rakefile = File.expand_path(DEFAULT_BUILDFILES.first) + fail "Buildfile already exists" if File.exist?(@rakefile) && !(tty_output? && agree('Buildfile exists, overwrite?')) + script = File.directory?(source) ? Generate.from_directory(source) : Generate.from_maven2_pom(source) + File.open @rakefile, 'w' do |file| + file.puts script + end + puts "Created #{@rakefile}" if verbose + @rakefile + end + + def raw_load_buildfile # replaces raw_load_rakefile + puts "(in #{Dir.pwd}, #{environment})" unless options.silent + load File.expand_path(@rakefile) if @rakefile && @rakefile != '' + options.rakelib.each do |rlib| + glob("#{rlib}/*.rake") do |name| + add_import name + end + end + load_imports + Buildr.projects + end + # 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? newly_installed = Util::Gems.install(*missing_deps) @@ -257,92 +427,106 @@ end end @gems = installed end - 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 - chdir(original_dir) { task('generate').invoke } - exit 1 - else - raise error - end - end - here = Dir.pwd + # 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 + versions = trail.scan(/[=><~!]{0,2}\s*[\d\.]+/) + versions = ['>= 0'] if versions.empty? + dep = Gem::Dependency.new(name, versions) + Gem::SourceIndex.from_installed_gems.search(dep).last || dep end end - def load_buildfile #:nodoc: - info "(in #{Dir.pwd}, #{environment})" - load File.expand_path(@rakefile) if @rakefile != '' - buildfile.enhance @requires.select { |f| File.file?(f) }.map{ |f| File.expand_path(f) } + # Load artifact specs from the build.yaml file, making them available + # by name ( ruby symbols ). + def load_artifact_ns #:nodoc: + hash = settings.build['artifacts'] + return unless hash + raise "Expected 'artifacts' element to be a hash" unless Hash === hash + # Currently we only use one artifact namespace to rule them all. (the root NS) + Buildr::ArtifactNamespace.load(:root => hash) end - - 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: + # TODO: this might need to be split up, look for deprecated features, better method name. 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. - files += Dir[File.expand_path('tasks/*.rake', original_dir)] + files += Dir[File.expand_path('tasks/*.rake')] files.each do |file| unless $LOADED_FEATURES.include?(file) load file $LOADED_FEATURES << file end end buildfile.enhance files true end - 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}" } + def display_tasks_and_comments + displayable_tasks = tasks.select { |t| t.comment && t.name =~ options.show_task_pattern } + if options.full_description + displayable_tasks.each do |t| + puts "buildr #{t.name_with_args}" + t.full_comment.split("\n").each do |line| + puts " #{line}" + end + puts end + else + width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10 + max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil + displayable_tasks.each do |t| + printf "#{name} %-#{width}s # %s\n", + t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment + end end end - - # Provide standard execption handling for the given block. - def standard_exception_handling + + def display_prerequisites + displayable_tasks = tasks.select { |t| t.name =~ options.show_task_pattern } + displayable_tasks.each do |t| + puts "buildr #{t.name}" + t.prerequisites.each { |pre| puts " #{pre}" } + end + end + + def standard_exception_handling # adds on_failure hook begin yield rescue SystemExit => ex # Exit silently with current status exit(ex.status) - rescue SystemExit, GetoptLong::InvalidOption => ex - # Exit silently + rescue OptionParser::ParseError => ex + $stderr.puts $terminal.color(ex.message, :red) 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 } + title, message = "Your build failed with an error", "#{Dir.pwd}:\n#{ex.message}" + @on_failure.each do |block| + block.call(title, message, ex) rescue nil + end # Exit with error message - $stderr.puts "buildr aborted!" + $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 ex.backtrace.select { |str| str =~ /#{rakefile}/ }.map { |line| $terminal.color(line, :red) }.join("\n") if rakefile $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 @@ -355,14 +539,10 @@ end class << self - task 'buildr:initialize' do - Buildr.load_tasks_and_local_files - end - # Returns the Buildr::Application object. def application Rake.application end @@ -397,42 +577,10 @@ else HighLine.use_color = false end -# 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 @@ -455,10 +603,17 @@ puts message if Buildr.application.options.trace end module Rake #:nodoc + # Rake's circular dependency checks (InvocationChain) only applies to task prerequisites, + # all other cases result in the non too-descriptive thread sleeping error. This change can + # deal with circular dependencies that occur from direct task invocation, e.g: + # task 'foo'=>'bar' + # task 'bar' do + # task('foo').invoke + # end 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) end @@ -484,6 +639,6 @@ Thread.current[:rake_chain] = nil end end end end -end \ No newline at end of file +end