# 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. require 'delegate' require 'drb/drb' module Buildr # This addon allows you start a DRb server hosting a buildfile, so that # you can later invoke tasks on it without having to load # the complete buildr runtime again. # # Usage: # # buildr -r buildr/drb drb:start # # Once the server has been started you can invoke tasks using a simple script: # # #!/usr/bin/env ruby # require 'rubygems' # require 'buildr/drb' # Buildr::DRbApplication.run # # Save this script as 'dbuildr', make it executable and use it to invoke tasks. # # dbuildr clean compile # # The dbuildr script will run as the server if there isn't one already running. # Subsequent calls to dbuildr will act as the client and invoke the tasks you # provide in the server. # If the buildfile has been modified it will be reloaded on the BuildrServer. # # JRuby users can use a nailgun client to invoke tasks as fast as possible # without having to incur JVM startup time. # See the documentation for buildr/nailgun. module DRbApplication port = ENV['DRB_PORT'] || 2111 PORT = port.to_i class SavedTask #:nodoc: def initialize(original) @original = original.clone @prerequisites = original.prerequisites.clone if original.respond_to?(:prerequisites) @actions = original.actions.clone if original.respond_to?(:actions) end def name @original.name end def actions @actions ||= [] end def prerequisites @prerequisites ||= [] end def define! @original.class.send(:define_task, @original.name => prerequisites).tap do |task| task.comment = @original.comment actions.each { |action| task.enhance &action } end end end # SavedTask class Snapshot #:nodoc: attr_accessor :projects, :tasks, :rules, :layout, :options # save the tasks,rules,layout defined by buildr def initialize @rules = Buildr.application.instance_eval { @rules || [] }.clone @options = Buildr.application.options.clone @options.rakelib ||= ['tasks'] @layout = Layout.default.clone @projects = Project.instance_eval { @projects || {} }.clone @tasks = Buildr.application.tasks.inject({}) do |hash, original| unless projects.key? original.name # don't save project definitions hash.update original.name => SavedTask.new(original) end hash end end end # Snapshot class << self attr_accessor :original, :snapshot def run begin client = connect rescue DRb::DRbConnError => e run_server! else run_client(client) end end def client_uri "druby://:#{PORT + 1}" end def remote_run(cfg) with_config(cfg) { Buildr.application.remote_run(self) } rescue => e cfg[:err].puts e.message e.backtrace.each { |b| cfg[:err].puts "\tfrom #{b}" } raise e end def save_snapshot(app) if app.instance_eval { @rakefile } @snapshot = self::Snapshot.new app.buildfile_reloaded! end end private def server_uri "druby://:#{PORT}" end def connect buildr = DRbObject.new(nil, server_uri) uri = buildr.client_uri # obtain our uri from the server DRb.start_service(uri) buildr end def run_client(client) client.remote_run :dir => Dir.pwd, :argv => ARGV, :in => $stdin, :out => $stdout, :err => $stderr end def setup unless original # Create the stdio delegator that can be cached (eg by fileutils) delegate_stdio # Lazily load buildr the first time it's needed require 'buildr' # Save the tasks,rules,layout defined by buildr # before loading any project @original = self::Snapshot.new Buildr.application.extend self save_snapshot(Buildr.application) end end def run_server setup DRb.start_service(server_uri, self) puts "#{self} waiting on #{server_uri}" end def run_server! setup if RUBY_PLATFORM[/java/] require 'buildr/nailgun' Buildr.application['nailgun:drb'].invoke else run_server DRb.thread.join end end def delegate_stdio $stdin = SimpleDelegator.new($stdin) $stdout = SimpleDelegator.new($stdout) $stderr = SimpleDelegator.new($stderr) end def with_config(remote) @invoked = true set = lambda do |env| ARGV.replace env[:argv] $stdin.__setobj__(env[:in]) $stdout.__setobj__(env[:out]) $stderr.__setobj__(env[:err]) Buildr.application.instance_variable_set :@original_dir, env[:dir] end original = { :dir => Buildr.application.instance_variable_get(:@original_dir), :argv => ARGV, :in => $stdin.__getobj__, :out => $stdout.__getobj__, :err => $stderr.__getobj__ } begin set[remote] yield ensure set[original] end end end # class << DRbApplication def remote_run(server) @options = server.original.options.clone init 'Distributed Buildr' if @rakefile if buildfile_needs_reload? reload_buildfile(server, server.original) else clear_invoked_tasks(server.snapshot || server.original) end else reload_buildfile(server, server.original) end top_level end def buildfile_reloaded! @last_loaded = buildfile.timestamp if @rakefile end private def buildfile_needs_reload? !@last_loaded || @last_loaded < buildfile.timestamp end def reload_buildfile(server, snapshot) clear_for_reload(snapshot) load_buildfile server.save_snapshot(self) end def clear_for_reload(snapshot) Project.clear @tasks = {} @rules = snapshot.rules.clone snapshot.tasks.each_pair { |name, saved| saved.define! } Layout.default = snapshot.layout.clone end def clear_invoked_tasks(snapshot) @rules = snapshot.rules.clone (@tasks.keys - snapshot.projects.keys).each do |name| if saved = snapshot.tasks[name] # reenable this task, restoring its actions/prereqs task = @tasks[name] task.reenable task.prerequisites.replace saved.prerequisites.clone task.actions.replace saved.actions.clone else # tasks generated at runtime, drop it @tasks.delete(name) end end end task('drb:start') { run_server! } if Buildr.respond_to?(:application) end # DRbApplication end