| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with this |
| # work for additional information regarding copyright ownership. The ASF |
| # licenses this file to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # 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 |
| # in the Software without restriction, including without limitation the rights |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| # copies of the Software, and to permit persons to whom the Software is |
| # furnished to do so, subject to the following conditions: |
| # |
| # The above copyright notice and this permission notice shall be included in |
| # all copies or substantial portions of the Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| # 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 '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 |
| # 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. |
| # Example for build settings are gems, repositories and artifacts used by that build. |
| # |
| # Profile settings are placed in the profiles.yaml file located in the build directory. They provide |
| # settings that differ in each environment the build runs in. For example, URLs and database |
| # connections will be different when used in development, test and production environments. |
| # 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 |
| |
| # Build settings loaded from build.yaml file in build directory. |
| attr_reader :build |
| |
| # Profiles loaded from profiles.yaml file in build directory. |
| attr_reader :profiles |
| |
| # :call-seq: |
| # profile => hash |
| # |
| # Returns the profile for the current environment. |
| def profile |
| profiles[@application.environment] ||= {} |
| 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) } |
| 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 |
| yaml |
| end |
| |
| end |
| |
| |
| class Application < Rake::Application #:nodoc: |
| |
| DEFAULT_BUILDFILES = ['buildfile', 'Buildfile'] + DEFAULT_RAKEFILES |
| |
| include CommandLineInterface |
| |
| attr_reader :rakefiles, :requires |
| private :rakefiles, :requires |
| |
| def initialize |
| super |
| @rakefiles = DEFAULT_BUILDFILES |
| @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) |
| @environment = ENV['BUILDR_ENV'] ||= 'development' |
| 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 |
| |
| # Returns the Settings associated with this build. |
| def settings |
| @settings ||= Settings.new(self) |
| end |
| |
| # :call-seq: |
| # buildfile |
| def buildfile |
| rakefile |
| end |
| |
| # :call-seq: |
| # build_files => files |
| # |
| # Returns a list of build files. These are files used by the build, |
| def build_files |
| [buildfile].compact + Array(@build_files) |
| end |
| |
| # 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 |
| private :listed_gems |
| |
| def run |
| times = Benchmark.measure do |
| standard_exception_handling do |
| find_buildfile |
| load_gems |
| load_artifacts |
| load_buildfile |
| top_level |
| load_tasks |
| 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 |
| 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 |
| |
| # 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 |
| 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 |
| end |
| end |
| @gems = installed |
| end |
| |
| def find_buildfile |
| here = Dir.pwd |
| 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 |
| end |
| end |
| |
| def load_buildfile |
| @requires.each { |name| require name } |
| puts "(in #{Dir.pwd}, #{environment})" |
| load File.expand_path(@rakefile) if @rakefile != '' |
| load_imports |
| end |
| |
| # Loads buildr.rake 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') ]. |
| 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| |
| unless $LOADED_FEATURES.include?(file) |
| load file |
| $LOADED_FEATURES << file |
| end |
| end |
| 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 |
| end |
| end |
| end |
| |
| # Not for external consumption. |
| def switch_to_namespace(names) #:nodoc: |
| current, @scope = @scope, names |
| begin |
| yield |
| ensure |
| @scope = current |
| end |
| end |
| |
| end |
| |
| |
| class << self |
| |
| task 'buildr:initialize' do |
| Buildr.load_tasks_and_local_files |
| end |
| |
| # Returns the Buildr::Application object. |
| def application |
| Rake.application |
| end |
| |
| def application=(app) #:nodoc: |
| Rake.application = app |
| end |
| |
| # Returns the Settings associated with this build. |
| def settings |
| Buildr.application.settings |
| end |
| |
| # Copied from BUILD_ENV. |
| def environment |
| Buildr.application.environment |
| end |
| |
| end |
| |
| Buildr.application = Buildr::Application.new |
| |
| end |
| |
| |
| # Add a touch of colors (red) to warnings. |
| if $stdout.isatty |
| begin |
| require 'Win32/Console/ANSI' if Config::CONFIG['host_os'] =~ /mswin/ |
| HighLine.use_color = true |
| rescue LoadError |
| end |
| 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) |
| end |
| end |
| 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) |
| end |
| |
| def invoke_with_call_chain(task_args, invocation_chain) |
| new_chain = InvocationChain.append(self, invocation_chain) |
| @lock.synchronize do |
| 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 |
| 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 |