#!/usr/bin/ruby1.8 -w # # Copyright:: Copyright 2009 Google Inc. # Original Author:: Ryan Brown (mailto:ribrdb@google.com) # # Licensed 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 'appengine-rack' require 'appengine-tools/boot' require 'appengine-tools/web-xml' require 'appengine-tools/xml-formatter' require 'fileutils' require 'yaml' module AppEngine module Admin class Application attr_reader :root def initialize(root) @root = root end def path(*pieces) File.join(@root, *pieces) end def webinf path('WEB-INF') end def webinf_lib path('WEB-INF', 'lib') end def generation_dir path('WEB-INF', 'appengine-generated') end def config_ru path('config.ru') end def web_xml path('WEB-INF', 'web.xml') end def aeweb_xml path('WEB-INF', 'appengine-web.xml') end def build_status path('WEB-INF', 'appengine-generated', 'build_status.yaml') end def public_root path(AppEngine::Rack.app.public_root) if AppEngine::Rack.app.public_root end def rack_app AppEngine::Rack.app end end class AppBundler EXISTING_JRUBY = /^(jruby-abridged|appengine-jruby)-.*jar$/ EXISTING_RACK = /jruby-rack.*jar$/ EXISTING_APIS = /^appengine-api.*jar$/ JRUBY_RACK = 'jruby-rack-0.9.5.jar' JRUBY_RACK_URL = 'http://kenai.com/projects/jruby-rack/' + "downloads/download/#{JRUBY_RACK}" RACKUP = %q{Dir.chdir('..') if Dir.pwd =~ /WEB-INF$/;} + %q{eval IO.read('config.ru'), nil, 'config.ru', 1} def initialize(root_path) @app = Application.new(root_path) end def bundle bundle_deps convert_config_ru end def bundle_deps create_webinf copy_jruby copy_rack copy_sdk end def app @app end def create_webinf Dir.mkdir(app.webinf) unless File.exists?(app.webinf) Dir.mkdir(app.webinf_lib) unless File.exists?(app.webinf_lib) Dir.mkdir(app.generation_dir) unless File.exists?(app.generation_dir) end def create_public return if app.public_root.nil? Dir.mkdir(app.public_root) unless File.exists?(app.public_root) end def convert_config_ru if !File.exists?(app.config_ru) if File.exists?(app.web_xml) unless File.exists?(app.aeweb_xml) puts "!! Error: you either need a #{app.config_ru} or " puts " #{app.aeweb_xml}." exit 1 end else # TODO auto generate a config.ru puts "!! Error: you need to create #{app.config_ru}." exit 1 end else generate_xml create_public end end def copy_jruby require 'appengine-jruby-jars' update_jars( "JRuby", EXISTING_JRUBY, [AppEngine::JRubyJars.jruby_jar], /rubygems/, [AppEngine::JRubyJars.rubygems_jar]) end def copy_rack update_jars('jruby-rack', EXISTING_RACK, [JRUBY_RACK]) do require 'open-uri' begin open(JRUBY_RACK_URL) do |src| open(File.join(app.webinf_lib, JRUBY_RACK), 'wb') do |dest| dest.write(src.read) end end rescue SocketError puts "Unable to download jruby-rack." puts "Please check your internet connection." end end end def copy_sdk require 'appengine-sdk' glob = "appengine-api-1.0-sdk-*.jar" jars = Dir.glob("#{AppEngine::SDK::SDK_ROOT}/lib/user/#{glob}") update_jars('appengine-sdk', EXISTING_APIS, jars) end private def find_jars(regex) Dir.entries(app.webinf_lib).grep(regex) rescue [] end def update_jars(name, regex, jars, opt_regex=nil, opt_jars=[]) existing = find_jars(regex) if existing.empty? message = "Installing #{name}" jars_to_install = jars + opt_jars else has_optional_jars = existing.any? {|j| j =~ opt_regex} expected_jars = jars expected_jars.concat(opt_jars) if has_optional_jars expected = expected_jars.map {|path| File.basename(path)} if existing.size != expected.size || (expected & existing) != expected message = "Updating #{name}" jars_to_install = expected_jars end end if jars_to_install puts message remove_jars(existing) if block_given? yield else FileUtils.cp(jars_to_install, app.webinf_lib) end end end def remove_jars(jars) paths = jars.map do |jar| "#{app.webinf_lib}/#{jar}" end FileUtils.rm_f(paths) end def valid_build return false unless File.exists? app.build_status return false unless File.exists? app.web_xml return false unless File.exists? app.aeweb_xml yaml = YAML.load_file app.build_status return false unless yaml.is_a? Hash return false unless File.stat(app.config_ru).mtime.eql? yaml[:config_ru] return false unless File.stat(app.web_xml).mtime.eql? yaml[:web_xml] return false unless File.stat(app.aeweb_xml).mtime.eql? yaml[:aeweb_xml] true end def generate_xml return if valid_build if defined? JRUBY_VERSION puts "=> Generating configuration files" Dir.glob("#{app.webinf_lib}/*.jar").each do |path| $: << path end app_root = app.root builder = WebXmlBuilder.new do # First configure the basic jruby-rack settings. add_jruby_rack_defaults(RACKUP) # Now read the user's rackup file # TODO generate a skeleton if it's missing Dir.chdir(app_root) do eval IO.read('config.ru'), nil, 'config.ru', 1 end end open(app.web_xml, 'w') do |webxml| xml = AppEngine::Rack::XmlFormatter.format(builder.to_xml) webxml.write(xml) end open(app.aeweb_xml, 'w') do |aeweb| xml = AppEngine::Rack::XmlFormatter.format(app.rack_app.to_xml) aeweb.write(xml) end yaml = { :config_ru => File.stat(app.config_ru).mtime, :aeweb_xml => File.stat(app.aeweb_xml).mtime, :web_xml => File.stat(app.web_xml).mtime } open(app.build_status, 'w') { |f| YAML.dump(yaml, f) } else AppEngine::Development.boot_jruby(app.root, :args => ['bundle', app.root], :exec => false) end end end def self.bundle_app(root_path) AppBundler.new(root_path).bundle end def self.bundle_deps(root_path) AppBundler.new(root_path).bundle_deps end end end