├── spec ├── rails4_spec.rb ├── bugs_spec.rb ├── no_lockfile_spec.rb ├── rails23_spec.rb ├── rails3_spec.rb ├── spec_helper.rb └── rubies_spec.rb ├── vendor ├── plugins │ └── taglib │ │ └── hi └── syck_hack.rb ├── .idea ├── .name ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── misc.xml ├── modules.xml ├── heroku-buildpack-ruby.iml └── .rakeTasks ├── .gitignore ├── Gemfile ├── bin ├── release ├── detect └── compile ├── lib ├── language_pack │ ├── no_lockfile.rb │ ├── disable_deploys.rb │ ├── bundler_lockfile.rb │ ├── rack.rb │ ├── shell_helpers.rb │ ├── rails4.rb │ ├── rails2.rb │ ├── rails3.rb │ ├── base.rb │ └── ruby.rb └── language_pack.rb ├── hatchet.json ├── LICENSE ├── Gemfile.lock ├── support └── s3 │ ├── hmac │ └── s3 ├── README.md ├── CHANGELOG.md └── Rakefile /spec/rails4_spec.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/taglib/hi: -------------------------------------------------------------------------------- 1 | hi -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | heroku-buildpack-ruby -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | repos/* 2 | .DS_Store 3 | vendor/bundler/* 4 | vendor/bundle/* 5 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | group :development do 4 | gem "heroku_hatchet" 5 | gem "rspec-core" 6 | gem "rspec-expectations" 7 | gem "excon" 8 | gem "rake" 9 | gem "heroku" 10 | end 11 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.expand_path("../../lib", __FILE__) 4 | require "language_pack" 5 | 6 | if pack = LanguagePack.detect(ARGV[0], ARGV[1]) 7 | puts pack.release 8 | end 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.expand_path("../../lib", __FILE__) 4 | require "language_pack" 5 | 6 | if pack = LanguagePack.detect(ARGV.first) 7 | puts pack.name 8 | exit 0 9 | else 10 | puts "no" 11 | exit 1 12 | end 13 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # sync output 4 | $stdout.sync = true 5 | 6 | $:.unshift File.expand_path("../../lib", __FILE__) 7 | require "language_pack" 8 | 9 | if pack = LanguagePack.detect(ARGV[0], ARGV[1]) 10 | pack.log("compile") do 11 | pack.compile 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/bugs_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe "Bugs" do 4 | context "MRI 1.8.7" do 5 | it "should install nokogiri" do 6 | Hatchet::AnvilApp.new("mri_187_nokogiri", :buildpack => buildpack).deploy do |app, heroku, output| 7 | expect(app).to be_deployed 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /spec/no_lockfile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "No Lockfile" do 4 | it "should not deploy" do 5 | Hatchet::AnvilApp.new("no_lockfile", :buildpack => buildpack).deploy do |app, heroku, output| 6 | expect(app).not_to be_deployed 7 | expect(output).to include("ERROR: Gemfile.lock required") 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/rails23_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe "Rails 2.3.x" do 4 | it "should deploy on ruby 1.8.7" do 5 | Hatchet::AnvilApp.new("rails23_mri_187", :buildpack => buildpack).deploy do |app, heroku| 6 | add_database(app, heroku) 7 | expect(app).to be_deployed 8 | expect(successful_body(app)).to eq("hello") 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/language_pack/no_lockfile.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "language_pack/base" 3 | 4 | class LanguagePack::NoLockfile < LanguagePack::Base 5 | def self.use? 6 | File.exist?("Gemfile") && !File.exists?("Gemfile.lock") 7 | end 8 | 9 | def name 10 | "Ruby/NoLockfile" 11 | end 12 | 13 | def compile 14 | error "Gemfile.lock required. Please check it in." 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /hatchet.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundler": [ 3 | "sharpstone/git_gemspec", 4 | "sharpstone/no_lockfile" 5 | ], 6 | "ruby": [ 7 | "sharpstone/mri_187" 8 | ], 9 | "rack": [ 10 | "sharpstone/mri_187_nokogiri", 11 | "sharpstone/mri_192", 12 | "sharpstone/mri_193", 13 | "sharpstone/mri_200" 14 | ], 15 | "rails2": [ 16 | "sharpstone/rails23_mri_187" 17 | ], 18 | "rails3": [ 19 | "sharpstone/rails3_mri_193", 20 | "sharpstone/railties3_mri_193" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /lib/language_pack/disable_deploys.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "language_pack/base" 3 | 4 | class LanguagePack::DisableDeploys < LanguagePack::Base 5 | def self.use? 6 | File.exist?("Gemfile") 7 | end 8 | 9 | def name 10 | "Ruby/DisableDeploys" 11 | end 12 | 13 | def compile 14 | error "Ruby deploys have been temporarily disabled due to a Rubygems.org security breach.\nPlease see https://status.heroku.com/incidents/489 for more info and a workaround if you need to deploy." 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/language_pack/bundler_lockfile.rb: -------------------------------------------------------------------------------- 1 | module LanguagePack 2 | module BundlerLockfile 3 | # checks if the Gemfile and Gemfile.lock exist 4 | def gemfile_lock? 5 | File.exist?('Gemfile') && File.exist?('Gemfile.lock') 6 | end 7 | 8 | # bootstraps bundler so we can use it before bundler is setup properlyLanguagePack::Ruby 9 | def bootstrap_bundler(&block) 10 | Dir.mktmpdir("bundler-") do |tmpdir| 11 | Dir.chdir(tmpdir) do 12 | system("curl #{LanguagePack::Base::VENDOR_URL}/#{LanguagePack::Ruby::BUNDLER_GEM_PATH}.tgz -s -o - | tar xzf -") 13 | end 14 | 15 | yield tmpdir 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/language_pack.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | # General Language Pack module 4 | module LanguagePack 5 | 6 | # detects which language pack to use 7 | # @param [Array] first argument is a String of the build directory 8 | # @return [LanguagePack] the {LanguagePack} detected 9 | def self.detect(*args) 10 | Dir.chdir(args.first) 11 | 12 | pack = [ NoLockfile, Rails4, Rails3, Rails2, Rack, Ruby ].detect do |klass| 13 | klass.use? 14 | end 15 | 16 | pack ? pack.new(*args) : nil 17 | end 18 | 19 | end 20 | 21 | require "language_pack/ruby" 22 | require "language_pack/rack" 23 | require "language_pack/rails2" 24 | require "language_pack/rails3" 25 | require "language_pack/disable_deploys" 26 | require "language_pack/rails4" 27 | require "language_pack/no_lockfile" 28 | -------------------------------------------------------------------------------- /spec/rails3_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe "Rails 3.x" do 4 | it "should deploy on ruby 1.9.3" do 5 | Hatchet::AnvilApp.new("rails3_mri_193", :buildpack => buildpack).deploy do |app, heroku| 6 | add_database(app, heroku) 7 | expect(app).to be_deployed 8 | expect(successful_body(app)).to eq("hello") 9 | end 10 | end 11 | 12 | context "when not using the rails gem" do 13 | it "should deploy on ruby 1.9.3" do 14 | Hatchet::AnvilApp.new("railties3_mri_193", :buildpack => buildpack).deploy do |app, heroku, output| 15 | add_database(app, heroku) 16 | expect(app).to be_deployed 17 | expect(output).to match("Ruby/Rails") 18 | expect(successful_body(app)).to eq("hello") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/core' 2 | require 'hatchet' 3 | require 'fileutils' 4 | require 'hatchet' 5 | 6 | ENV['RACK_ENV'] = 'test' 7 | 8 | RSpec.configure do |config| 9 | config.filter_run :focused => true 10 | config.run_all_when_everything_filtered = true 11 | config.alias_example_to :fit, :focused => true 12 | 13 | config.expect_with :rspec do |c| 14 | c.syntax = :expect 15 | end 16 | config.mock_with :none 17 | end 18 | 19 | def buildpack 20 | File.expand_path(File.dirname(__FILE__) + "/..") 21 | end 22 | 23 | def git_repo 24 | "https://github.com/heroku/heroku-buildpack-ruby.git" 25 | end 26 | 27 | def add_database(app, heroku) 28 | heroku.post_addon(app.name, 'heroku-postgresql:dev') 29 | _, value = heroku.get_config_vars(app.name).body.detect {|key, value| key.match(/HEROKU_POSTGRESQL_[A-Z]+_URL/) } 30 | heroku.put_config_vars(app.name, 'DATABASE_URL' => value) 31 | end 32 | 33 | def successful_body(app) 34 | Excon.get("http://#{app.name}.herokuapp.com", :idempotent => true, :expects => 200, :retry_limit => 10).body 35 | end 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License: 2 | 3 | Copyright (C) 2012 Heroku, Inc. 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /lib/language_pack/rack.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "language_pack/ruby" 3 | 4 | # Rack Language Pack. This is for any non-Rails Rack apps like Sinatra. 5 | class LanguagePack::Rack < LanguagePack::Ruby 6 | 7 | # detects if this is a valid Rack app by seeing if "config.ru" exists 8 | # @return [Boolean] true if it's a Rack app 9 | def self.use? 10 | gemfile_lock? && LanguagePack::Ruby.gem_version('rack') 11 | end 12 | 13 | def name 14 | "Ruby/Rack" 15 | end 16 | 17 | def default_config_vars 18 | super.merge({ 19 | "RACK_ENV" => "production" 20 | }) 21 | end 22 | 23 | def default_process_types 24 | # let's special case thin here if we detect it 25 | web_process = gem_is_bundled?("thin") ? 26 | "bundle exec thin start -R config.ru -e $RACK_ENV -p $PORT" : 27 | "bundle exec rackup config.ru -p $PORT" 28 | 29 | super.merge({ 30 | "web" => web_process 31 | }) 32 | end 33 | 34 | private 35 | 36 | # sets up the profile.d script for this buildpack 37 | def setup_profiled 38 | super 39 | set_env_default "RACK_ENV", "production" 40 | end 41 | 42 | end 43 | 44 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (3.2.13) 5 | i18n (= 0.6.1) 6 | multi_json (~> 1.0) 7 | addressable (2.3.4) 8 | anvil-cli (0.15.0) 9 | progress (~> 2.4.0) 10 | rest-client (~> 1.6.7) 11 | thor (~> 0.15.2) 12 | diff-lcs (1.1.3) 13 | excon (0.16.10) 14 | heroku (2.37.2) 15 | heroku-api (~> 0.3.7) 16 | launchy (>= 0.3.2) 17 | netrc (~> 0.7.7) 18 | rest-client (~> 1.6.1) 19 | rubyzip 20 | heroku-api (0.3.8) 21 | excon (~> 0.16.10) 22 | heroku_hatchet (0.0.1) 23 | activesupport 24 | anvil-cli 25 | excon 26 | heroku-api 27 | thor 28 | i18n (0.6.1) 29 | launchy (2.3.0) 30 | addressable (~> 2.3) 31 | mime-types (1.22) 32 | multi_json (1.7.2) 33 | netrc (0.7.7) 34 | progress (2.4.0) 35 | rake (10.0.4) 36 | rest-client (1.6.7) 37 | mime-types (>= 1.16) 38 | rspec-core (2.13.1) 39 | rspec-expectations (2.12.1) 40 | diff-lcs (~> 1.1.3) 41 | rubyzip (0.9.9) 42 | thor (0.15.4) 43 | 44 | PLATFORMS 45 | ruby 46 | 47 | DEPENDENCIES 48 | excon 49 | heroku 50 | heroku_hatchet 51 | rake 52 | rspec-core 53 | rspec-expectations 54 | -------------------------------------------------------------------------------- /.idea/heroku-buildpack-ruby.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /spec/rubies_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative 'spec_helper' 2 | 3 | describe "Ruby Versions" do 4 | it "should deploy ruby 1.8.7 properly" do 5 | Hatchet::AnvilApp.new("mri_187", :buildpack => buildpack).deploy do |app, heroku, output| 6 | expect(app).to be_deployed 7 | expect(successful_body(app).chomp).to eq("ruby 1.8.7 (2012-10-12 patchlevel 371) [x86_64-linux]") 8 | end 9 | end 10 | 11 | it "should deploy ruby 1.9.2 properly" do 12 | Hatchet::AnvilApp.new("mri_192", :buildpack => buildpack).deploy do |app, heroku, output| 13 | expect(app).to be_deployed 14 | expect(successful_body(app).chomp).to eq("ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-linux]") 15 | end 16 | end 17 | 18 | it "should deploy ruby 1.9.2 properly (git)" do 19 | Hatchet::GitApp.new("mri_192", :buildpack => git_repo).deploy do |app, heroku, output| 20 | expect(app).to be_deployed 21 | expect(successful_body(app).chomp).to eq("ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-linux]") 22 | end 23 | end 24 | 25 | it "should deploy ruby 1.9.3 properly" do 26 | Hatchet::AnvilApp.new("mri_193", :buildpack => buildpack).deploy do |app, heroku, output| 27 | expect(app).to be_deployed 28 | expect(successful_body(app).chomp).to eq("ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-linux]") 29 | end 30 | end 31 | 32 | it "should deploy ruby 2.0.0 properly" do 33 | Hatchet::AnvilApp.new("mri_200", :buildpack => buildpack).deploy do |app, heroku| 34 | expect(app).to be_deployed 35 | expect(successful_body(app).chomp).to eq("ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-linux]") 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/language_pack/shell_helpers.rb: -------------------------------------------------------------------------------- 1 | module LanguagePack 2 | module ShellHelpers 3 | # display error message and stop the build process 4 | # @param [String] error message 5 | def error(message) 6 | Kernel.puts " !" 7 | message.split("\n").each do |line| 8 | Kernel.puts " ! #{line.strip}" 9 | end 10 | Kernel.puts " !" 11 | log "exit", :error => message 12 | exit 1 13 | end 14 | 15 | # run a shell comannd and pipe stderr to stdout 16 | # @param [String] command to be run 17 | # @return [String] output of stdout and stderr 18 | def run(command) 19 | %x{ #{command} 2>&1 } 20 | end 21 | 22 | # run a shell command and pipe stderr to /dev/null 23 | # @param [String] command to be run 24 | # @return [String] output of stdout 25 | def run_stdout(command) 26 | %x{ #{command} 2>/dev/null } 27 | end 28 | 29 | # run a shell command and stream the output 30 | # @param [String] command to be run 31 | def pipe(command) 32 | output = "" 33 | IO.popen(command) do |io| 34 | until io.eof? 35 | buffer = io.gets 36 | output << buffer 37 | puts buffer 38 | end 39 | end 40 | 41 | output 42 | end 43 | 44 | # display a topic message 45 | # (denoted by ----->) 46 | # @param [String] topic message to be displayed 47 | def topic(message) 48 | Kernel.puts "-----> #{message}" 49 | $stdout.flush 50 | end 51 | 52 | # display a message in line 53 | # (indented by 6 spaces) 54 | # @param [String] message to be displayed 55 | def puts(message) 56 | message.split("\n").each do |line| 57 | super " #{line.strip}" 58 | end 59 | $stdout.flush 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /vendor/syck_hack.rb: -------------------------------------------------------------------------------- 1 | # :stopdoc: 2 | 3 | # Hack to handle syck's DefaultKey bug 4 | # 5 | # This file is always loaded AFTER either syck or psych are already 6 | # loaded. It then looks at what constants are available and creates 7 | # a consistent view on all rubys. 8 | # 9 | # All this is so that there is always a YAML::Syck::DefaultKey 10 | # class no matter if the full yaml library has loaded or not. 11 | # 12 | 13 | $: << ENV['BUNDLER_LIB_PATH'] if ENV['BUNDLER_LIB_PATH'] 14 | require 'bundler/psyched_yaml' 15 | 16 | module YAML 17 | # In newer 1.9.2, there is a Syck toplevel constant instead of it 18 | # being underneith YAML. If so, reference it back under YAML as 19 | # well. 20 | if defined? ::Syck 21 | Syck = ::Syck 22 | 23 | # Otherwise, if there is no YAML::Syck, then we've got just psych 24 | # loaded, so lets define a stub for DefaultKey. 25 | elsif !defined? YAML::Syck 26 | module Syck 27 | class DefaultKey 28 | end 29 | end 30 | end 31 | 32 | # Now that we've got something that is always here, define #to_s 33 | # so when code tries to use this, it at least just shows up like it 34 | # should. 35 | module Syck 36 | class DefaultKey 37 | def to_s 38 | '=' 39 | end 40 | end 41 | end 42 | end 43 | 44 | # Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML 45 | # to be a toplevel constant. So gemspecs created under these versions of Syck 46 | # will have references to Syck::DefaultKey. 47 | # 48 | # So we need to be sure that we reference Syck at the toplevel too so that 49 | # we can always load these kind of gemspecs. 50 | # 51 | if !defined?(Syck) 52 | Syck = YAML::Syck 53 | end 54 | 55 | # Now that we've got Syck setup in all the right places, store 56 | # a reference to the DefaultKey class inside Gem. We do this so that 57 | # if later on YAML, etc are redefined, we've still got a consistent 58 | # place to find the DefaultKey class for comparison. 59 | 60 | module Gem 61 | SyckDefaultKey = YAML::Syck::DefaultKey 62 | end 63 | 64 | # :startdoc: 65 | -------------------------------------------------------------------------------- /lib/language_pack/rails4.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "language_pack/rails3" 3 | 4 | # Rails 4 Language Pack. This is for all Rails 4.x apps. 5 | class LanguagePack::Rails4 < LanguagePack::Rails3 6 | # detects if this is a Rails 3.x app 7 | # @return [Boolean] true if it's a Rails 3.x app 8 | def self.use? 9 | if gemfile_lock? 10 | rails_version = LanguagePack::Ruby.gem_version('railties') 11 | rails_version >= Gem::Version.new('4.0.0.beta') && rails_version < Gem::Version.new('5.0.0') if rails_version 12 | end 13 | end 14 | 15 | def name 16 | "Ruby/Rails" 17 | end 18 | 19 | def default_process_types 20 | web_process = gem_is_bundled?("thin") ? 21 | "bin/rails server thin -p $PORT -e $RAILS_ENV" : 22 | "bin/rails server -p $PORT -e $RAILS_ENV" 23 | super.merge({ 24 | "web" => web_process, 25 | "console" => "bin/rails console" 26 | }) 27 | end 28 | 29 | private 30 | def plugins 31 | [] 32 | end 33 | 34 | def run_assets_precompile_rake_task 35 | log("assets_precompile") do 36 | setup_database_url_env 37 | 38 | if rake_task_defined?("assets:precompile") 39 | topic("Preparing app for Rails asset pipeline") 40 | if File.exists?("public/assets/manifest.yml") 41 | puts "Detected manifest.yml, assuming assets were compiled locally" 42 | else 43 | ENV["RAILS_GROUPS"] ||= "assets" 44 | ENV["RAILS_ENV"] ||= "production" 45 | 46 | puts "Running: rake assets:precompile" 47 | require 'benchmark' 48 | time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") } 49 | 50 | if $?.success? 51 | log "assets_precompile", :status => "success" 52 | puts "Asset precompilation completed (#{"%.2f" % time}s)" 53 | else 54 | log "assets_precompile", :status => "failure" 55 | error "Precompiling assets failed." 56 | end 57 | end 58 | else 59 | puts "Error detecting the assets:precompile task" 60 | end 61 | end 62 | end 63 | 64 | def create_database_yml 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /support/s3/hmac: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Implement HMAC functionality on top of the OpenSSL digest functions. 3 | # licensed under the terms of the GNU GPL v2 4 | # Copyright 2007 Victor Lowther 5 | 6 | die() { 7 | echo $* 8 | exit 1 9 | } 10 | 11 | check_deps() { 12 | local res=0 13 | while [ $# -ne 0 ]; do 14 | which "${1}" >& /dev/null || { res=1; echo "${1} not found."; } 15 | shift 16 | done 17 | (( res == 0 )) || die "aborting." 18 | } 19 | 20 | # write a byte (passed as hex) to stdout 21 | write_byte() { 22 | # $1 = byte to write 23 | printf "\\x$(printf "%x" ${1})" 24 | } 25 | 26 | # make an hmac pad out of a key. 27 | # this is not the most secure way of doing it, but it is 28 | # the most expedient. 29 | make_hmac_pad() { 30 | # using key in file $1 and byte in $2, create the appropriate hmac pad 31 | # Pad keys out to $3 bytes 32 | # if key is longer than $3, use hash $4 to hash the key first. 33 | local x y a size remainder oifs 34 | (( remainder = ${3} )) 35 | # in case someone else was messing with IFS. 36 | for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-); 37 | do 38 | write_byte $((${x} ^ ${2})) 39 | (( remainder -= 1 )) 40 | done 41 | for ((y=0; remainder - y ;y++)); do 42 | write_byte $((0 ^ ${2})) 43 | done 44 | } 45 | 46 | # utility functions for making hmac pads 47 | hmac_ipad() { 48 | make_hmac_pad "${1}" 0x36 ${2} "${3}" 49 | } 50 | 51 | hmac_opad() { 52 | make_hmac_pad "${1}" 0x5c ${2} "${3}" 53 | } 54 | 55 | # hmac something 56 | do_hmac() { 57 | # $1 = algo to use. Must be one that openssl knows about 58 | # $2 = keyfile to use 59 | # $3 = file to hash. uses stdin if none is given. 60 | # accepts input on stdin, leaves it on stdout. 61 | # Output is binary, if you want something else pipe it accordingly. 62 | local blocklen keysize x 63 | case "${1}" in 64 | sha) blocklen=64 ;; 65 | sha1) blocklen=64 ;; 66 | md5) blocklen=64 ;; 67 | md4) blocklen=64 ;; 68 | sha256) blocklen=64 ;; 69 | sha512) blocklen=128 ;; 70 | *) die "Unknown hash ${1} passed to hmac!" ;; 71 | esac 72 | cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \ 73 | cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary 74 | } 75 | 76 | [[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}". 77 | 78 | check_deps od openssl 79 | do_hmac "${@}" 80 | -------------------------------------------------------------------------------- /lib/language_pack/rails2.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "language_pack" 3 | require "language_pack/rack" 4 | 5 | # Rails 2 Language Pack. This is for any Rails 2.x apps. 6 | class LanguagePack::Rails2 < LanguagePack::Ruby 7 | 8 | # detects if this is a valid Rails 2 app 9 | # @return [Boolean] true if it's a Rails 2 app 10 | def self.use? 11 | if gemfile_lock? 12 | rails_version = LanguagePack::Ruby.gem_version('rails') 13 | rails_version >= Gem::Version.new('2.0.0') && rails_version < Gem::Version.new('3.0.0') if rails_version 14 | end 15 | end 16 | 17 | def name 18 | "Ruby/Rails" 19 | end 20 | 21 | def default_config_vars 22 | super.merge({ 23 | "RAILS_ENV" => "production", 24 | "RACK_ENV" => "production" 25 | }) 26 | end 27 | 28 | def default_process_types 29 | web_process = gem_is_bundled?("thin") ? 30 | "bundle exec thin start -e $RAILS_ENV -p $PORT" : 31 | "bundle exec ruby script/server -p $PORT" 32 | 33 | super.merge({ 34 | "web" => web_process, 35 | "worker" => "bundle exec rake jobs:work", 36 | "console" => "bundle exec script/console" 37 | }) 38 | end 39 | 40 | def compile 41 | super 42 | install_plugins 43 | end 44 | 45 | private 46 | 47 | # list of plugins to be installed 48 | # @return [Array] resulting list in a String Array 49 | def plugins 50 | %w( rails_log_stdout ) 51 | end 52 | 53 | # the root path of where the plugins are to be installed from 54 | # @return [String] the resulting path 55 | def plugin_root 56 | File.expand_path("../../../vendor/plugins", __FILE__) 57 | end 58 | 59 | # vendors all the plugins into the slug 60 | def install_plugins 61 | topic "Rails plugin injection" 62 | plugins.each { |plugin| install_plugin(plugin) } 63 | end 64 | 65 | # vendors an individual plugin 66 | # @param [String] name of the plugin 67 | def install_plugin(name) 68 | plugin_dir = "vendor/plugins/#{name}" 69 | return if File.exist?(plugin_dir) 70 | puts "Injecting #{name}" 71 | FileUtils.mkdir_p plugin_dir 72 | Dir.chdir(plugin_dir) do |dir| 73 | run("curl #{VENDOR_URL}/#{name}.tgz -s -o - | tar xzf -") 74 | end 75 | end 76 | 77 | # most rails apps need a database 78 | # @return [Array] shared database addon 79 | def add_dev_database_addon 80 | ['heroku-postgresql:dev'] 81 | end 82 | 83 | # sets up the profile.d script for this buildpack 84 | def setup_profiled 85 | super 86 | set_env_default "RACK_ENV", "production" 87 | set_env_default "RAILS_ENV", "production" 88 | end 89 | 90 | end 91 | 92 | -------------------------------------------------------------------------------- /.idea/.rakeTasks: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /lib/language_pack/rails3.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "language_pack/rails2" 3 | 4 | # Rails 3 Language Pack. This is for all Rails 3.x apps. 5 | class LanguagePack::Rails3 < LanguagePack::Rails2 6 | # detects if this is a Rails 3.x app 7 | # @return [Boolean] true if it's a Rails 3.x app 8 | def self.use? 9 | if gemfile_lock? 10 | rails_version = LanguagePack::Ruby.gem_version('railties') 11 | rails_version >= Gem::Version.new('3.0.0') && rails_version < Gem::Version.new('4.0.0') if rails_version 12 | end 13 | end 14 | 15 | def name 16 | "Ruby/Rails" 17 | end 18 | 19 | def default_process_types 20 | # let's special case thin here 21 | web_process = gem_is_bundled?("thin") ? 22 | "bundle exec thin start -R config.ru -e $RAILS_ENV -p $PORT" : 23 | "bundle exec rails server -p $PORT" 24 | 25 | super.merge({ 26 | "web" => web_process, 27 | "console" => "bundle exec rails console" 28 | }) 29 | end 30 | 31 | private 32 | 33 | def plugins 34 | super.concat(%w( rails3_serve_static_assets )).uniq 35 | end 36 | 37 | # runs the tasks for the Rails 3.1 asset pipeline 38 | def run_assets_precompile_rake_task 39 | log("assets_precompile") do 40 | setup_database_url_env 41 | 42 | if rake_task_defined?("assets:precompile") 43 | topic("Preparing app for Rails asset pipeline") 44 | if File.exists?("public/assets/manifest.yml") 45 | puts "Detected manifest.yml, assuming assets were compiled locally" 46 | else 47 | ENV["RAILS_GROUPS"] ||= "assets" 48 | ENV["RAILS_ENV"] ||= "production" 49 | 50 | puts "Running: rake assets:precompile" 51 | require 'benchmark' 52 | time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") } 53 | 54 | if $?.success? 55 | log "assets_precompile", :status => "success" 56 | puts "Asset precompilation completed (#{"%.2f" % time}s)" 57 | else 58 | log "assets_precompile", :status => "failure" 59 | puts "Precompiling assets failed, enabling runtime asset compilation" 60 | install_plugin("rails31_enable_runtime_asset_compilation") 61 | puts "Please see this article for troubleshooting help:" 62 | puts "http://devcenter.heroku.com/articles/rails31_heroku_cedar#troubleshooting" 63 | end 64 | end 65 | end 66 | end 67 | end 68 | 69 | # setup the database url as an environment variable 70 | def setup_database_url_env 71 | ENV["DATABASE_URL"] ||= begin 72 | # need to use a dummy DATABASE_URL here, so rails can load the environment 73 | scheme = 74 | if gem_is_bundled?("pg") 75 | "postgres" 76 | elsif gem_is_bundled?("mysql") 77 | "mysql" 78 | elsif gem_is_bundled?("mysql2") 79 | "mysql2" 80 | elsif gem_is_bundled?("sqlite3") || gem_is_bundled?("sqlite3-ruby") 81 | "sqlite3" 82 | end 83 | "#{scheme}://user:pass@127.0.0.1/dbname" 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/language_pack/base.rb: -------------------------------------------------------------------------------- 1 | require "language_pack" 2 | require "pathname" 3 | require "yaml" 4 | require "digest/sha1" 5 | require "language_pack/shell_helpers" 6 | 7 | Encoding.default_external = Encoding::UTF_8 if defined?(Encoding) 8 | 9 | # abstract class that all the Ruby based Language Packs inherit from 10 | class LanguagePack::Base 11 | include LanguagePack::ShellHelpers 12 | 13 | VENDOR_URL = "https://s3.amazonaws.com/heroku-buildpack-ruby" 14 | 15 | attr_reader :build_path, :cache_path 16 | 17 | # changes directory to the build_path 18 | # @param [String] the path of the build dir 19 | # @param [String] the path of the cache dir 20 | def initialize(build_path, cache_path=nil) 21 | @build_path = build_path 22 | @cache_path = cache_path 23 | @id = Digest::SHA1.hexdigest("#{Time.now.to_f}-#{rand(1000000)}")[0..10] 24 | 25 | Dir.chdir build_path 26 | end 27 | 28 | def self.===(build_path) 29 | raise "must subclass" 30 | end 31 | 32 | # name of the Language Pack 33 | # @return [String] the result 34 | def name 35 | raise "must subclass" 36 | end 37 | 38 | # list of default addons to install 39 | def default_addons 40 | raise "must subclass" 41 | end 42 | 43 | # config vars to be set on first push. 44 | # @return [Hash] the result 45 | # @not: this is only set the first time an app is pushed to. 46 | def default_config_vars 47 | raise "must subclass" 48 | end 49 | 50 | # process types to provide for the app 51 | # Ex. for rails we provide a web process 52 | # @return [Hash] the result 53 | def default_process_types 54 | raise "must subclass" 55 | end 56 | 57 | # this is called to build the slug 58 | def compile 59 | end 60 | 61 | # collection of values passed for a release 62 | # @return [String] in YAML format of the result 63 | def release 64 | setup_language_pack_environment 65 | 66 | { 67 | "addons" => default_addons, 68 | "default_process_types" => default_process_types 69 | }.to_yaml 70 | end 71 | 72 | # log output 73 | # Ex. log "some_message", "here", :someattr="value" 74 | def log(*args) 75 | args.concat [:id => @id] 76 | args.concat [:framework => self.class.to_s.split("::").last.downcase] 77 | 78 | start = Time.now.to_f 79 | log_internal args, :start => start 80 | 81 | if block_given? 82 | begin 83 | ret = yield 84 | finish = Time.now.to_f 85 | log_internal args, :status => "complete", :finish => finish, :elapsed => (finish - start) 86 | return ret 87 | rescue StandardError => ex 88 | finish = Time.now.to_f 89 | log_internal args, :status => "error", :finish => finish, :elapsed => (finish - start), :message => ex.message 90 | raise ex 91 | end 92 | end 93 | end 94 | 95 | private ################################## 96 | 97 | # sets up the environment variables for the build process 98 | def setup_language_pack_environment 99 | end 100 | 101 | def add_to_profiled(string) 102 | FileUtils.mkdir_p "#{build_path}/.profile.d" 103 | File.open("#{build_path}/.profile.d/ruby.sh", "a") do |file| 104 | file.puts string 105 | end 106 | end 107 | 108 | def set_env_default(key, val) 109 | add_to_profiled "export #{key}=${#{key}:-#{val}}" 110 | end 111 | 112 | def set_env_override(key, val) 113 | add_to_profiled %{export #{key}="#{val.gsub('"','\"')}"} 114 | end 115 | 116 | def log_internal(*args) 117 | message = build_log_message(args) 118 | %x{ logger -p user.notice -t "slugc[$$]" "buildpack-ruby #{message}" } 119 | end 120 | 121 | def build_log_message(args) 122 | args.map do |arg| 123 | case arg 124 | when Float then "%0.2f" % arg 125 | when Array then build_log_message(arg) 126 | when Hash then arg.map { |k,v| "#{k}=#{build_log_message([v])}" }.join(" ") 127 | else arg 128 | end 129 | end.join(" ") 130 | end 131 | 132 | # create a Pathname of the cache dir 133 | # @return [Pathname] the cache dir 134 | def cache_base 135 | Pathname.new(cache_path) 136 | end 137 | 138 | # removes the the specified 139 | # @param [String] relative path from the cache_base 140 | def cache_clear(path) 141 | target = (cache_base + path) 142 | target.exist? && target.rmtree 143 | end 144 | 145 | # write cache contents 146 | # @param [String] path of contents to store. it will be stored using this a relative path from the cache_base. 147 | # @param [Boolean] defaults to true. if set to true, the cache store directory will be cleared before writing to it. 148 | def cache_store(path, clear_first=true) 149 | cache_clear(path) if clear_first 150 | cache_copy path, (cache_base + path) 151 | end 152 | 153 | # load cache contents 154 | # @param [String] relative path of the cache contents 155 | def cache_load(path) 156 | cache_copy (cache_base + path), path 157 | end 158 | 159 | # copy cache contents 160 | # @param [String] source directory 161 | # @param [String] destination directory 162 | def cache_copy(from, to) 163 | return false unless File.exist?(from) 164 | FileUtils.mkdir_p File.dirname(to) 165 | system("cp -a #{from}/. #{to}") 166 | end 167 | 168 | # check if the cache content exists 169 | # @param [String] relative path of the cache contents 170 | # @param [Boolean] true if the path exists in the cache and false if otherwise 171 | def cache_exists?(path) 172 | File.exists?(cache_base + path) 173 | end 174 | end 175 | 176 | -------------------------------------------------------------------------------- /support/s3/s3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # basic amazon s3 operations 3 | # Licensed under the terms of the GNU GPL v2 4 | # Copyright 2007 Victor Lowther 5 | 6 | set -e 7 | 8 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 9 | PATH="$basedir:$PATH" 10 | 11 | # print a message and bail 12 | die() { 13 | echo $* 14 | exit 1 15 | } 16 | 17 | # check to see if the variable name passed exists and holds a value. 18 | # Die if it does not. 19 | check_or_die() { 20 | [[ ${!1} ]] || die "Environment variable ${1} is not set." 21 | } 22 | 23 | # check to see if we have all the needed S3 variables defined. 24 | # Bail if we do not. 25 | check_s3() { 26 | local sak x 27 | for x in S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY; do 28 | check_or_die ${x}; 29 | done 30 | sak="$(echo -n $S3_SECRET_ACCESS_KEY | wc -c)" 31 | (( ${sak%%[!0-9 ]*} == 40 )) || \ 32 | die "S3 Secret Access Key is not exactly 40 bytes long. Please fix it." 33 | } 34 | # check to see if our external dependencies exist 35 | check_dep() { 36 | local res=0 37 | while [[ $# -ne 0 ]]; do 38 | which "${1}" >& /dev/null || { res=1; echo "${1} not found."; } 39 | shift 40 | done 41 | (( res == 0 )) || die "aborting." 42 | } 43 | 44 | check_deps() { 45 | check_dep openssl date hmac cat grep curl 46 | check_s3 47 | } 48 | 49 | urlenc() { 50 | # $1 = string to url encode 51 | # output is on stdout 52 | # we don't urlencode everything, just enough stuff. 53 | echo -n "${1}" | 54 | sed 's/%/%25/g 55 | s/ /%20/g 56 | s/#/%23/g 57 | s/\$/%24/g 58 | s/\&/%26/g 59 | s/+/%2b/g 60 | s/,/%2c/g 61 | s/:/%3a/g 62 | s/;/%3b/g 63 | s/?/%3f/g 64 | s/@/%40/g 65 | s/ /%09/g' 66 | } 67 | 68 | xmldec() { 69 | # no parameters. 70 | # accept input on stdin, put it on stdout. 71 | # patches accepted to get more stuff 72 | sed 's/\"/\"/g 73 | s/\&/\&/g 74 | s/\<//g' 76 | } 77 | 78 | ## basic S3 functionality. x-amz-header functionality is not implemented. 79 | # make an S3 signature string, which will be output on stdout. 80 | s3_signature_string() { 81 | # $1 = HTTP verb 82 | # $2 = date string, must be in UTC 83 | # $3 = bucket name, if any 84 | # $4 = resource path, if any 85 | # $5 = content md5, if any 86 | # $6 = content MIME type, if any 87 | # $7 = canonicalized headers, if any 88 | # signature string will be output on stdout 89 | local verr="Must pass a verb to s3_signature_string!" 90 | local verb="${1:?verr}" 91 | local bucket="${3}" 92 | local resource="${4}" 93 | local derr="Must pass a date to s3_signature_string!" 94 | local date="${2:?derr}" 95 | local mime="${6}" 96 | local md5="${5}" 97 | local headers="${7}" 98 | printf "%s\n%s\n%s\n%s\n%s\n%s%s" \ 99 | "${verb}" "${md5}" "${mime}" "${date}" \ 100 | "${headers}" "${bucket}" "${resource}" | \ 101 | hmac sha1 "${S3_SECRET_ACCESS_KEY}" | openssl base64 -e -a 102 | } 103 | 104 | # cheesy, but it is the best way to have multiple headers. 105 | curl_headers() { 106 | # each arg passed will be output on its own line 107 | local parms=$# 108 | for ((;$#;)); do 109 | echo "header = \"${1}\"" 110 | shift 111 | done 112 | } 113 | 114 | s3_curl() { 115 | # invoke curl to do all the heavy HTTP lifting 116 | # $1 = method (one of GET, PUT, or DELETE. HEAD is not handled yet.) 117 | # $2 = remote bucket. 118 | # $3 = remote name 119 | # $4 = local name. 120 | local bucket remote date sig md5 arg inout headers 121 | # header handling is kinda fugly, but it works. 122 | bucket="${2:+/${2}}/" # slashify the bucket 123 | remote="$(urlenc "${3}")" # if you don't, strange things may happen. 124 | stdopts="--connect-timeout 10 --fail --silent" 125 | [[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail" 126 | case "${1}" in 127 | GET) arg="-o" inout="${4:--}" # stdout if no $4 128 | ;; 129 | PUT) [[ ${2} ]] || die "PUT can has bucket?" 130 | if [[ ! ${3} ]]; then 131 | arg="-X PUT" 132 | headers[${#headers[@]}]="Content-Length: 0" 133 | elif [[ -f ${4} ]]; then 134 | md5="$(openssl dgst -md5 -binary "${4}"|openssl base64 -e -a)" 135 | arg="-T" inout="${4}" 136 | headers[${#headers[@]}]="x-amz-acl: public-read" 137 | headers[${#headers[@]}]="Expect: 100-continue" 138 | else 139 | die "Cannot write non-existing file ${4}" 140 | fi 141 | ;; 142 | DELETE) arg="-X DELETE" 143 | ;; 144 | HEAD) arg="-I" ;; 145 | *) die "Unknown verb ${1}. It probably would not have worked anyways." ;; 146 | esac 147 | date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')" 148 | sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "" "x-amz-acl:public-read") 149 | 150 | headers[${#headers[@]}]="Authorization: AWS ${S3_ACCESS_KEY_ID}:${sig}" 151 | headers[${#headers[@]}]="Date: ${date}" 152 | [[ ${md5} ]] && headers[${#headers[@]}]="Content-MD5: ${md5}" 153 | curl ${arg} "${inout}" ${stdopts} -o - -K <(curl_headers "${headers[@]}") \ 154 | "http://s3.amazonaws.com${bucket}${remote}" 155 | return $? 156 | } 157 | 158 | s3_put() { 159 | # $1 = remote bucket to put it into 160 | # $2 = remote name to put 161 | # $3 = file to put. This must be present if $2 is. 162 | s3_curl PUT "${1}" "${2}" "${3:-${2}}" 163 | return $? 164 | } 165 | 166 | s3_get() { 167 | # $1 = bucket to get file from 168 | # $2 = remote file to get 169 | # $3 = local file to get into. Will be overwritten if it exists. 170 | # If this contains a path, that path must exist before calling this. 171 | s3_curl GET "${1}" "${2}" "${3:-${2}}" 172 | return $? 173 | } 174 | 175 | s3_test() { 176 | # same args as s3_get, but uses the HEAD verb instead of the GET verb. 177 | s3_curl HEAD "${1}" "${2}" >/dev/null 178 | return $? 179 | } 180 | 181 | # Hideously ugly, but it works well enough. 182 | s3_buckets() { 183 | s3_get |grep -o '[^>]*' |sed 's/<[^>]*>//g' |xmldec 184 | return $? 185 | } 186 | 187 | # this will only return the first thousand entries, alas 188 | # Mabye some kind soul can fix this without writing an XML parser in bash? 189 | # Also need to add xml entity handling. 190 | s3_list() { 191 | # $1 = bucket to list 192 | [ "x${1}" == "x" ] && return 1 193 | s3_get "${1}" |grep -o '[^>]*' |sed 's/<[^>]*>//g'| xmldec 194 | return $? 195 | } 196 | 197 | s3_delete() { 198 | # $1 = bucket to delete from 199 | # $2 = item to delete 200 | s3_curl DELETE "${1}" "${2}" 201 | return $? 202 | } 203 | 204 | # because this uses s3_list, it suffers from the same flaws. 205 | s3_rmrf() { 206 | # $1 = bucket to delete everything from 207 | s3_list "${1}" | while read f; do 208 | s3_delete "${1}" "${f}"; 209 | done 210 | } 211 | 212 | check_deps 213 | case $1 in 214 | put) shift; s3_put "$@" ;; 215 | get) shift; s3_get "$@" ;; 216 | rm) shift; s3_delete "$@" ;; 217 | ls) shift; s3_list "$@" ;; 218 | test) shift; s3_test "$@" ;; 219 | buckets) s3_buckets ;; 220 | rmrf) shift; s3_rmrf "$@" ;; 221 | *) die "Unknown command ${1}." 222 | ;; 223 | esac 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Heroku buildpack: Ruby 2 | ====================== 3 | 4 | This is a [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Ruby, Rack, and Rails apps. It uses [Bundler](http://gembundler.com) for dependency management. 5 | 6 | Usage 7 | ----- 8 | 9 | ### Ruby 10 | 11 | Example Usage: 12 | 13 | $ ls 14 | Gemfile Gemfile.lock 15 | 16 | $ heroku create --stack cedar --buildpack https://github.com/heroku/heroku-buildpack-ruby.git 17 | 18 | $ git push heroku master 19 | ... 20 | -----> Heroku receiving push 21 | -----> Fetching custom buildpack 22 | -----> Ruby app detected 23 | -----> Installing dependencies using Bundler version 1.1.rc 24 | Running: bundle install --without development:test --path vendor/bundle --deployment 25 | Fetching gem metadata from http://rubygems.org/.. 26 | Installing rack (1.3.5) 27 | Using bundler (1.1.rc) 28 | Your bundle is complete! It was installed into ./vendor/bundle 29 | Cleaning up the bundler cache. 30 | -----> Discovering process types 31 | Procfile declares types -> (none) 32 | Default types for Ruby -> console, rake 33 | 34 | The buildpack will detect your app as Ruby if it has a `Gemfile` and `Gemfile.lock` files in the root directory. It will then proceed to run `bundle install` after setting up the appropriate environment for [ruby](http://ruby-lang.org) and [Bundler](http://gembundler.com). 35 | 36 | #### Run the Tests 37 | 38 | Clone the repo, then `bundle install` then clone the test fixtures by running: 39 | 40 | ```sh 41 | $ hatchet install 42 | ``` 43 | 44 | Now run the tests: 45 | 46 | ```sh 47 | $ bundle exec rspec spec 48 | ``` 49 | 50 | Now go take a nap or something for a really long time. 51 | 52 | #### Bundler 53 | 54 | For non-windows `Gemfile.lock` files, the `--deployment` flag will be used. In the case of windows, the Gemfile.lock will be deleted and Bundler will do a full resolve so native gems are handled properly. The `vendor/bundle` directory is cached between builds to allow for faster `bundle install` times. `bundle clean` is used to ensure no stale gems are stored between builds. 55 | 56 | ### Rails 2 57 | 58 | Example Usage: 59 | 60 | $ ls 61 | app config db doc Gemfile Gemfile.lock lib log public Rakefile README script test tmp vendor 62 | 63 | $ ls config/environment.rb 64 | config/environment.rb 65 | 66 | $ heroku create --stack cedar --buildpack https://github.com/heroku/heroku-buildpack-ruby.git 67 | 68 | $ git push heroku master 69 | ... 70 | -----> Heroku receiving push 71 | -----> Ruby/Rails app detected 72 | -----> Installing dependencies using Bundler version 1.1.rc 73 | ... 74 | -----> Writing config/database.yml to read from DATABASE_URL 75 | -----> Rails plugin injection 76 | Injecting rails_log_stdout 77 | -----> Discovering process types 78 | Procfile declares types -> (none) 79 | Default types for Ruby/Rails -> console, rake, web, worker 80 | 81 | The buildpack will detect your app as a Rails 2 app if it has a `environment.rb` file in the `config` directory. 82 | 83 | #### Rails Log STDOUT 84 | A [rails_log_stdout](http://github.com/ddollar/rails_log_stdout) is installed by default so Rails' logger will log to STDOUT and picked up by Heroku's [logplex](http://github.com/heroku/logplex). 85 | 86 | #### Auto Injecting Plugins 87 | 88 | Any vendored plugin can be stopped from being installed by creating the directory it's installed to in the slug. For instance, to prevent rails_log_stdout plugin from being injected, add `vendor/plugins/rails_log_stdout/.gitkeep` to your git repo. 89 | 90 | ### Rails 3 91 | 92 | Example Usage: 93 | 94 | $ ls 95 | app config config.ru db doc Gemfile Gemfile.lock lib log Procfile public Rakefile README script tmp vendor 96 | 97 | $ ls config/application.rb 98 | config/application.rb 99 | 100 | $ heroku create --stack cedar --buildpack https://github.com/heroku/heroku-buildpack-ruby.git 101 | 102 | $ git push heroku master 103 | -----> Heroku receiving push 104 | -----> Ruby/Rails app detected 105 | -----> Installing dependencies using Bundler version 1.1.rc 106 | Running: bundle install --without development:test --path vendor/bundle --deployment 107 | ... 108 | -----> Writing config/database.yml to read from DATABASE_URL 109 | -----> Preparing app for Rails asset pipeline 110 | Running: rake assets:precompile 111 | -----> Rails plugin injection 112 | Injecting rails_log_stdout 113 | Injecting rails3_serve_static_assets 114 | -----> Discovering process types 115 | Procfile declares types -> web 116 | Default types for Ruby/Rails -> console, rake, worker 117 | 118 | The buildpack will detect your apps as a Rails 3 app if it has an `application.rb` file in the `config` directory. 119 | 120 | #### Assets 121 | 122 | To enable static assets being served on the dyno, [rails3_serve_static_assets](http://github.com/pedro/rails3_serve_static_assets) is installed by default. If the [execjs gem](http://github.com/sstephenson/execjs) is detected then [node.js](http://github.com/joyent/node) will be vendored. The `assets:precompile` rake task will get run if no `public/manifest.yml` is detected. See [this article](http://devcenter.heroku.com/articles/rails31_heroku_cedar) on how rails 3.1 works on cedar. 123 | 124 | Hacking 125 | ------- 126 | 127 | To use this buildpack, fork it on Github. Push up changes to your fork, then create a test app with `--buildpack ` and push to it. 128 | 129 | To change the vendored binaries for Bundler, [Node.js](http://github.com/joyent/node), and rails plugins, use the rake tasks provided by the `Rakefile`. You'll need an S3-enabled AWS account and a bucket to store your binaries in as well as the [vulcan](http://github.com/heroku/vulcan) gem to build the binaries on heroku. 130 | 131 | For example, you can change the vendored version of Bundler to 1.1.rc. 132 | 133 | First you'll need to build a Heroku-compatible version of Node.js: 134 | 135 | $ export AWS_ID=xxx AWS_SECRET=yyy S3_BUCKET=zzz 136 | $ s3 create $S3_BUCKET 137 | $ rake gem:install[bundler,1.1.rc] 138 | 139 | Open `lib/language_pack/ruby.rb` in your editor, and change the following line: 140 | 141 | BUNDLER_VERSION = "1.1.rc" 142 | 143 | Open `lib/language_pack/base.rb` in your editor, and change the following line: 144 | 145 | VENDOR_URL = "https://s3.amazonaws.com/zzz" 146 | 147 | Commit and push the changes to your buildpack to your Github fork, then push your sample app to Heroku to test. You should see: 148 | 149 | -----> Installing dependencies using Bundler version 1.1.rc 150 | 151 | NOTE: You'll need to vendor the plugins, node, Bundler, and libyaml by running the rake tasks for the buildpack to work properly. 152 | 153 | Flow 154 | ---- 155 | 156 | Here's the basic flow of how the buildpack works: 157 | 158 | Ruby (Gemfile and Gemfile.lock is detected) 159 | 160 | * runs Bundler 161 | * installs binaries 162 | * installs node if the gem execjs is detected 163 | * runs `rake assets:precompile` if the rake task is detected 164 | 165 | Rack (config.ru is detected) 166 | 167 | * everything from Ruby 168 | * sets RACK_ENV=production 169 | 170 | Rails 2 (config/environment.rb is detected) 171 | 172 | * everything from Rack 173 | * sets RAILS_ENV=production 174 | * install rails 2 plugins 175 | * [rails_log_stdout](http://github.com/ddollar/rails_log_stdout) 176 | 177 | Rails 3 (config/application.rb is detected) 178 | 179 | * everything from Rails 2 180 | * install rails 3 plugins 181 | * [rails3_server_static_assets](https://github.com/pedro/rails3_serve_static_assets) 182 | 183 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v61 (4/18/2013) 2 | 3 | Features: 4 | 5 | * Start caching the rubygems version used. 6 | 7 | Bugfixes: 8 | 9 | * Rebuild bundler cache if rubygems 2 is detected. Bugfixes in later rubygems. 10 | 11 | ## v60 (4/17/2013) 12 | 13 | Security: 14 | 15 | * Disable Java RMI Remote Classloading for CVE-2013-1537, 16 | 17 | ## v59 (4/4/2013) 18 | 19 | Bugfixes: 20 | 21 | * Change JVM S3 bucket 22 | 23 | ## v58 (3/19/2013) 24 | 25 | Bugfixes: 26 | 27 | * Fix ruby 1.8.7 not being able to compile native extensions 28 | 29 | ## v57 (3/18/2013) 30 | 31 | Bugfixes: 32 | 33 | * Fix git gemspec bug in bundler 34 | 35 | ## v56 (3/11/2013) 36 | 37 | Bugfixes: 38 | 39 | * Upgrade bundler to 1.3.2 to fix --dry-clean/Would have removed bug in bundle clean, part 2. 40 | 41 | ## v55 (3/7/2013) 42 | 43 | Bugfixes: 44 | 45 | * Revert back to Bundler 1.3.0.pre.5, see https://gist.github.com/mattonrails/e063caf86962995e7ba0 46 | 47 | ## v54 (3/7/2013) 48 | 49 | Bugfixes: 50 | 51 | * Upgrade bundler to 1.3.2 to fix --dry-clean/Would have removed bug in bundle clean 52 | 53 | ## v53 (3/6/2013) 54 | 55 | Bugfixes: 56 | 57 | * bin/detect for Rails 3 and 4 will use railties for detection vs the rails gem 58 | * bin/detect does not error out when Gemfile + Gemfile.lock are missing 59 | 60 | ## v52 (2/25/2013) 61 | 62 | Bugfixes: 63 | 64 | * Revert back to 1.3.0.pre.5 due to bundler warnings 65 | 66 | ## v51 (2/25/2013) 67 | 68 | Features: 69 | 70 | * Initial Rails 4 beta support 71 | * Upgrade bundler to 1.3.0 72 | 73 | Bugfixes: 74 | 75 | * Better buildpack detection through Gemfile.lock gems 76 | 77 | ## v50 (1/31/2013) 78 | 79 | Features: 80 | 81 | * Restore ruby deploys back to normal 82 | 83 | ## v49 (1/30/2013) 84 | 85 | Features: 86 | 87 | * Re-enable ruby deploys for apps just using the heroku cache 88 | * Display ruby version change when busting the cache 89 | 90 | ## v48 (1/30/2013) 91 | 92 | Features: 93 | 94 | * Update deploy error message copy to link to status incident. 95 | 96 | ## v47 (1/30/2013) 97 | 98 | Features: 99 | 100 | * Disable ruby deploys due to rubygems.org compromise 101 | 102 | ## v46 (1/10/2013) 103 | 104 | Features: 105 | 106 | * Upgrade Bundler to 1.3.0.pre.5 107 | * bundler binstubs now go in vendor/bundle/bin 108 | 109 | ## v45 (12/14/2012) 110 | 111 | Features: 112 | 113 | * Stop setting env vars in bin/release now that login-shell is released 114 | * Enable Invoke Dynamic on JRuby by default 115 | * GEM_PATH is now updated on each push 116 | 117 | ## v44 (12/14/2012) 118 | 119 | Faulty Release 120 | 121 | ## v43 (12/13/2012) 122 | 123 | Features: 124 | 125 | * Upgrade Bundler to 1.3.0.pre.2 126 | 127 | ## v42 (11/26/2012) 128 | 129 | Features: 130 | 131 | * Upgrade Bundler to 1.2.2 to fix Ruby 2.0.0/YAML issues 132 | 133 | ## v41 (11/1/2012) 134 | 135 | Features: 136 | 137 | * Enable ruby 2.0.0 support for testing 138 | 139 | ## v40 (10/14/2012) 140 | 141 | Features: 142 | 143 | * Cache version of the buildpack we used to deploy 144 | * Purge cache when v38 is detected 145 | 146 | ## v39 (10/14/2012) 147 | 148 | Bugfixes: 149 | 150 | * Don't display cache clearing message for new apps 151 | * Actually clear bundler cache on ruby version change 152 | 153 | ## v38 (10/14/2012) 154 | 155 | Bugfixes: 156 | 157 | * Stop bundle cache from continually growing 158 | 159 | ## v37 (10/12/2012) 160 | 161 | Bugfixes: 162 | 163 | * Remove temporary workaround from v36. 164 | * Clear bundler cache upon Ruby version change 165 | 166 | ## v36 (10/12/2012) 167 | 168 | Bugfixes: 169 | 170 | * Always clear the cache for ruby 1.9.3 as a temporary workaround due to the security upgrade 171 | 172 | ## v35 (9/19/2012) 173 | 174 | Features: 175 | 176 | * Upgrade to Bundler 1.2.1 177 | * Display bundle clean output 178 | * More resilent to rubygems.org API outages 179 | 180 | Bugfixes: 181 | 182 | * `bundle clean` works again 183 | 184 | ## v34 (8/30/2012) 185 | 186 | Features: 187 | 188 | * Upgrade to Bundler 1.2.0 189 | 190 | ## v33 (8/9/2012) 191 | 192 | Features: 193 | 194 | * Upgrade to Bundler 1.2.0.rc.2 195 | * vendor JDK7 for JRuby, but disable invoke dynamic 196 | 197 | ## v29 (7/19/2012) 198 | 199 | Features: 200 | 201 | * support .profile.d/ruby.sh 202 | * sync stdout so that the buildpack streams even in non-interactive shells 203 | * Upgrade to Bundler 1.2.0.rc 204 | 205 | ## v28 (7/16/2012) 206 | 207 | Features: 208 | 209 | * Vendor OpenJDK6 into slug when using JRuby 210 | * ruby version support for ruby 1.8.7 via bundler's ruby DSL 211 | 212 | Bugfixes: 213 | 214 | * sqlite3 error gets displayed again 215 | 216 | ## v27 (6/14/2012) 217 | 218 | Bugfixes: 219 | 220 | * Remove `vendor/bundle` message only appears when dir actually exists 221 | 222 | ## v26 (6/14/2012) 223 | 224 | Features: 225 | 226 | * print message when assets:precompile finishes successfully 227 | * Remove `vendor/bundle` if user commits it to their git repo. 228 | 229 | ## v25 (6/12/2012) 230 | 231 | Features: 232 | 233 | * support "ruby-xxx-jruby-yyy" for jruby detection packages 234 | 235 | ## v24 (6/7/2012) 236 | 237 | Features: 238 | 239 | * removes bundler cache in the slug, to minimize slug size (@stevenh512, #16) 240 | * optimize push time with caching 241 | 242 | ## v23 (5/8/2012) 243 | 244 | Bugfixes: 245 | 246 | * fix ruby version bug with "fatal:-Not-a-git-repository" 247 | 248 | ## v22 (5/7/2012) 249 | 250 | Features: 251 | 252 | * bundler 1.2.0.pre 253 | * ruby version support for ruby 1.9.2/1.9.3 via bundler's ruby DSL 254 | 255 | Deprecation: 256 | 257 | * ENV['RUBY_VERSION'] in favor of bundler's ruby DSL 258 | 259 | ## v21 (3/21/2012) 260 | 261 | Features: 262 | 263 | * bundler 1.1.2 264 | 265 | ## v20 (3/12/2012) 266 | 267 | Features: 268 | 269 | * bundler 1.1.0 \o/ 270 | 271 | ## v19 (1/25/2012) 272 | 273 | Bugfixes: 274 | 275 | * fix native extension building for rbx 2.0.0dev 276 | 277 | ## v18 (1/18/2012) 278 | 279 | Features: 280 | 281 | * JRuby support 282 | * rbx 2.0.0dev support 283 | 284 | Bugfixes: 285 | 286 | * force db password to be a string in the yaml file 287 | 288 | ## v17 (12/29/2011) 289 | 290 | Features: 291 | 292 | * bundler 1.1.rc.7 293 | 294 | ## v16 (12/29/2011) 295 | 296 | Features: 297 | 298 | * pass DATABASE_URL to rails 3.1 assets:precompile rake task detection 299 | 300 | ## v15 (12/27/2011) 301 | 302 | Features: 303 | 304 | * bundler 1.1.rc.6 305 | 306 | ## v14 (12/22/2011) 307 | 308 | Bugfixes: 309 | 310 | * stop freedom patching syck in ruby 1.9.3+ 311 | 312 | ## v13 (12/15/2011) 313 | 314 | Features: 315 | 316 | * bundler 1.1.rc.5 317 | 318 | ## v12 (12/13/2011) 319 | 320 | Bugfixes: 321 | 322 | * syck workaround for yaml/psych issues 323 | 324 | ## v11 (12/12/2011) 325 | 326 | Features: 327 | 328 | * bundler 1.1.rc.3 329 | 330 | ## v10 (11/23/2011) 331 | 332 | Features: 333 | 334 | * bundler binstubs 335 | * dynamic slug_vendor_base detection 336 | 337 | Bugfixes: 338 | 339 | * don't show sqlite3 error if it's in a bundle without group on failed bundle install 340 | 341 | ## v9 (11/14/2011) 342 | 343 | Features: 344 | 345 | * rbx 1.2.4 support 346 | * print out RUBY_VERSION being used 347 | 348 | Bugfixes: 349 | 350 | * don't leave behind ruby_versions.yml 351 | 352 | ## v8 (11/8/2011) 353 | 354 | Features: 355 | 356 | * use vm as part of RUBY_VERSION 357 | 358 | ## v7 (11/8/2011) 359 | 360 | Features: 361 | 362 | * ruby 1.9.3 support 363 | * specify ruby versions using RUBY_VERSION build var 364 | 365 | Bugfixes: 366 | 367 | * move "bin/" to the front of the PATH, so apps can override existing bins 368 | 369 | ## v6 (11/2/2011) 370 | 371 | Features: 372 | 373 | * add sqlite3 warning when detected on bundle install error 374 | 375 | Bugfixes: 376 | 377 | * Change gem detection to use lockfile parser 378 | * use `$RACK_ENV` when thin is detected for rack apps 379 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "tmpdir" 3 | 4 | S3_BUCKET_NAME = "heroku-buildpack-ruby" 5 | VENDOR_URL = "https://s3.amazonaws.com/#{S3_BUCKET_NAME}" 6 | 7 | def s3_tools_dir 8 | File.expand_path("../support/s3", __FILE__) 9 | end 10 | 11 | def s3_upload(tmpdir, name) 12 | sh("#{s3_tools_dir}/s3 put #{S3_BUCKET_NAME} #{name}.tgz #{tmpdir}/#{name}.tgz") 13 | end 14 | 15 | def vendor_plugin(git_url, branch = nil) 16 | name = File.basename(git_url, File.extname(git_url)) 17 | Dir.mktmpdir("#{name}-") do |tmpdir| 18 | FileUtils.rm_rf("#{tmpdir}/*") 19 | 20 | Dir.chdir(tmpdir) do 21 | sh "git clone #{git_url} ." 22 | sh "git checkout origin/#{branch}" if branch 23 | FileUtils.rm_rf("#{name}/.git") 24 | sh("tar czvf #{tmpdir}/#{name}.tgz *") 25 | s3_upload(tmpdir, name) 26 | end 27 | end 28 | end 29 | 30 | def in_gem_env(gem_home, &block) 31 | old_gem_home = ENV['GEM_HOME'] 32 | old_gem_path = ENV['GEM_PATH'] 33 | ENV['GEM_HOME'] = ENV['GEM_PATH'] = gem_home.to_s 34 | 35 | yield 36 | 37 | ENV['GEM_HOME'] = old_gem_home 38 | ENV['GEM_PATH'] = old_gem_path 39 | end 40 | 41 | def install_gem(gem, version) 42 | name = "#{gem}-#{version}" 43 | Dir.mktmpdir("#{gem}-#{version}") do |tmpdir| 44 | Dir.chdir(tmpdir) do |dir| 45 | FileUtils.rm_rf("#{tmpdir}/*") 46 | 47 | in_gem_env(tmpdir) do 48 | sh("gem install #{gem} --version #{version} --no-ri --no-rdoc --env-shebang") 49 | sh("tar czvf #{tmpdir}/#{name}.tgz *") 50 | s3_upload(tmpdir, name) 51 | end 52 | end 53 | end 54 | end 55 | 56 | def build_ruby_command(name, output, prefix, usr_dir, tmpdir, rubygems = nil) 57 | vulcan_prefix = "/app/vendor/#{output}" 58 | build_command = [ 59 | # need to move libyaml/libffi to dirs we can see 60 | "mv #{usr_dir} /tmp", 61 | "./configure --enable-load-relative --disable-install-doc --prefix #{prefix}", 62 | "env CPATH=/tmp/#{usr_dir}/include:\\$CPATH CPPATH=/tmp/#{usr_dir}/include:\\$CPPATH LIBRARY_PATH=/tmp/#{usr_dir}/lib:\\$LIBRARY_PATH make", 63 | "make install" 64 | ] 65 | build_command << "#{prefix}/bin/ruby /tmp/#{usr_dir}/rubygems-#{rubygems}/setup.rb" if rubygems 66 | build_command << "mv #{prefix} /app/vendor/#{output}" if prefix != "/app/vendor/#{output}" 67 | build_command = build_command.join(" && ") 68 | 69 | sh "vulcan build -v -o #{output}.tgz --prefix #{vulcan_prefix} --source #{name} --command=\"#{build_command}\"" 70 | s3_upload(tmpdir, output) 71 | end 72 | 73 | def build_rbx_command(name, output, prefix, usr_dir, tmpdir, ruby_version) 74 | build_command = [ 75 | # need to move libyaml/libffi to dirs we can see 76 | "mv usr /tmp", 77 | "ls /tmp/#{usr_dir}", 78 | "./configure --prefix #{prefix} --enable-version=#{ruby_version} --default-version=#{ruby_version} --with-include-dir=/tmp/#{usr_dir}/include --with-lib-dir=/tmp/#{usr_dir}/lib", 79 | "rake install" 80 | ] 81 | # build_command << "mv #{prefix} /app/vendor/#{name}" if name != output 82 | build_command = build_command.join(" && ") 83 | 84 | sh "vulcan build -v -o #{output}.tgz --source #{name} --prefix #{prefix} --command=\"#{build_command}\"" 85 | s3_upload(tmpdir, output) 86 | end 87 | 88 | desc "update plugins" 89 | task "plugins:update" do 90 | vendor_plugin "http://github.com/heroku/rails_log_stdout.git", "legacy" 91 | vendor_plugin "http://github.com/pedro/rails3_serve_static_assets.git" 92 | vendor_plugin "http://github.com/hone/rails31_enable_runtime_asset_compilation.git" 93 | end 94 | 95 | desc "install vendored gem" 96 | task "gem:install", :gem, :version do |t, args| 97 | gem = args[:gem] 98 | version = args[:version] 99 | 100 | install_gem(gem, version) 101 | end 102 | 103 | desc "install libyaml" 104 | task "libyaml:install", :version do |t, args| 105 | version = args[:version] 106 | name = "libyaml-#{version}" 107 | Dir.mktmpdir("libyaml-") do |tmpdir| 108 | Dir.chdir(tmpdir) do |dir| 109 | FileUtils.rm_rf("#{tmpdir}/*") 110 | prefix = "/app/vendor/yaml-#{version}" 111 | 112 | sh "curl http://pyyaml.org/download/libyaml/yaml-#{version}.tar.gz -s -o - | tar vzxf -" 113 | 114 | build_command = [ 115 | "env CFLAGS=-fPIC ./configure --enable-static --disable-shared --prefix=#{prefix}", 116 | "make", 117 | "make install" 118 | ].join(" && ") 119 | 120 | sh "vulcan build -v -o #{name}.tgz --source yaml-#{version} --prefix=#{prefix} --command=\"#{build_command}\"" 121 | s3_upload(tmpdir, name) 122 | end 123 | end 124 | end 125 | 126 | desc "install node" 127 | task "node:install", :version do |t, args| 128 | version = args[:version] 129 | name = "node-#{version}" 130 | prefix = "/app/vendor/node-v#{version}" 131 | Dir.mktmpdir("node-") do |tmpdir| 132 | Dir.chdir(tmpdir) do |dir| 133 | FileUtils.rm_rf("#{tmpdir}/*") 134 | 135 | sh "curl http://nodejs.org/dist/node-v#{version}.tar.gz -s -o - | tar vzxf -" 136 | 137 | build_command = [ 138 | "./configure --prefix #{prefix}", 139 | "make install", 140 | "mv #{prefix}/bin/node #{prefix}/.", 141 | "rm -rf #{prefix}/include", 142 | "rm -rf #{prefix}/lib", 143 | "rm -rf #{prefix}/share", 144 | "rm -rf #{prefix}/bin" 145 | ].join(" && ") 146 | 147 | sh "vulcan build -v -o #{name}.tgz --source node-v#{version} --command=\"#{build_command}\"" 148 | s3_upload(tmpdir, name) 149 | end 150 | end 151 | end 152 | 153 | desc "install ruby" 154 | task "ruby:install", :version do |t, args| 155 | full_version = args[:version] 156 | full_name = "ruby-#{full_version}" 157 | version = full_version.split('-').first 158 | name = "ruby-#{version}" 159 | usr_dir = "usr" 160 | rubygems = nil 161 | Dir.mktmpdir("ruby-") do |tmpdir| 162 | Dir.chdir(tmpdir) do |dir| 163 | FileUtils.rm_rf("#{tmpdir}/*") 164 | 165 | major_ruby = version.match(/\d\.\d/)[0] 166 | rubygems = "1.8.24" if major_ruby == "1.8" 167 | sh "curl http://ftp.ruby-lang.org/pub/ruby/#{major_ruby}/#{full_name}.tar.gz -s -o - | tar zxf -" 168 | FileUtils.mkdir_p("#{full_name}/#{usr_dir}") 169 | Dir.chdir("#{full_name}/#{usr_dir}") do 170 | sh "curl #{VENDOR_URL}/libyaml-0.1.4.tgz -s -o - | tar zxf -" 171 | sh "curl #{VENDOR_URL}/libffi-3.0.10.tgz -s -o - | tar zxf -" 172 | sh "curl http://production.cf.rubygems.org/rubygems/rubygems-#{rubygems}.tgz -s -o - | tar xzf -" if major_ruby == "1.8" 173 | end 174 | 175 | # runtime ruby 176 | prefix = "/app/vendor/#{name}" 177 | build_ruby_command(full_name, name, prefix, usr_dir, tmpdir, rubygems) 178 | 179 | # build ruby 180 | if major_ruby == "1.8" 181 | output = "ruby-build-#{version}" 182 | prefix = "/tmp/ruby-#{version}" 183 | build_ruby_command(full_name, output, prefix, usr_dir, tmpdir, rubygems) 184 | end 185 | end 186 | end 187 | end 188 | 189 | desc "install rbx" 190 | task "rbx:install", :version do |t, args| 191 | version = args[:version] 192 | name = "rubinius-#{version}" 193 | output = "rbx-#{version}" 194 | prefix = "/app/vendor/#{output}" 195 | 196 | Dir.mktmpdir("rbx-") do |tmpdir| 197 | Dir.chdir(tmpdir) do |dir| 198 | FileUtils.rm_rf("#{tmpdir}/*") 199 | 200 | sh "curl http://asset.rubini.us/#{name}.tar.gz -s -o - | tar vzxf -" 201 | build_command = [ 202 | "./configure --prefix #{prefix}", 203 | "rake install" 204 | ].join(" && ") 205 | 206 | sh "vulcan build -v -o #{output}.tgz --source #{name} --prefix #{prefix} --command=\"#{build_command}\"" 207 | s3_upload(tmpdir, output) 208 | end 209 | end 210 | end 211 | 212 | desc "install rbx 2.0.0dev" 213 | task "rbx2dev:install", :version, :ruby_version do |t, args| 214 | version = args[:version] 215 | ruby_version = args[:ruby_version] 216 | source = "rubinius-#{version}" 217 | name = "rubinius-2.0.0dev" 218 | output = "rbx-#{version}-#{ruby_version}" 219 | usr_dir = "usr" 220 | 221 | Dir.mktmpdir("rbx-") do |tmpdir| 222 | Dir.chdir(tmpdir) do |dir| 223 | FileUtils.rm_rf("#{tmpdir}/*") 224 | 225 | sh "curl http://asset.rubini.us/#{source}.tar.gz -s -o - | tar vzxf -" 226 | FileUtils.mkdir_p("#{name}/#{usr_dir}") 227 | Dir.chdir("#{name}/#{usr_dir}") do 228 | sh "curl #{VENDOR_URL}/libyaml-0.1.4.tgz -s -o - | tar vzxf -" 229 | sh "curl #{VENDOR_URL}/libffi-3.0.10.tgz -s -o - | tar vzxf -" 230 | end 231 | 232 | prefix = "/app/vendor/#{output}" 233 | build_rbx_command(name, output, prefix, usr_dir, tmpdir, ruby_version) 234 | 235 | # rbx build 236 | prefix = "/tmp/#{output}" 237 | output = "rbx-build-#{version}-#{ruby_version}" 238 | build_rbx_command(name, output, prefix, usr_dir, tmpdir, ruby_version) 239 | end 240 | end 241 | end 242 | 243 | desc "install jruby" 244 | task "jruby:install", :version, :ruby_version do |t, args| 245 | version = args[:version] 246 | ruby_version = args[:ruby_version] 247 | name = "jruby-src-#{version}" 248 | src_folder = "jruby-#{version}" 249 | output = "ruby-#{ruby_version}-jruby-#{version}" 250 | launcher = "launcher" 251 | 252 | Dir.mktmpdir("jruby-") do |tmpdir| 253 | Dir.chdir(tmpdir) do 254 | sh "curl http://jruby.org.s3.amazonaws.com/downloads/#{version}/#{name}.tar.gz -s -o - | tar vzxf -" 255 | sh "rm -rf test" 256 | Dir.chdir(src_folder) do 257 | sh "curl http://www.nic.funet.fi/pub/mirrors/apache.org/ant/binaries/apache-ant-1.8.4-bin.tar.gz -s -o - | tar vxzf -" 258 | sh "rm -rf manual" 259 | end 260 | Dir.chdir("#{src_folder}/bin") do 261 | sh "curl #{VENDOR_URL}/jruby-launcher-1.0.12-java.tgz -s -o - | tar vzxf -" 262 | end 263 | 264 | major, minor, patch = ruby_version.split('.') 265 | 266 | build_command = [ 267 | "apache-ant-1.8.4/bin/ant -Djruby.default.ruby.version=#{major}.#{minor}", 268 | "rm bin/*.bat", 269 | "rm bin/*.dll", 270 | "rm bin/*.exe", 271 | "ln -s jruby bin/ruby", 272 | "mkdir -p /app/vendor/#{output}", 273 | "mv bin /app/vendor/#{output}", 274 | "mv lib /app/vendor/#{output}" 275 | ] 276 | build_command = build_command.join(" && ") 277 | sh "vulcan build -v -o #{output}.tgz --prefix /app/vendor/#{output} --source #{src_folder} --command=\"#{build_command}\"" 278 | 279 | s3_upload(tmpdir, output) 280 | end 281 | end 282 | end 283 | 284 | desc "build the jruby-launcher" 285 | task "jruby:launcher", :version do |t, args| 286 | version = args[:version] 287 | name = "jruby-launcher-#{version}-java" 288 | prefix = "/tmp/jruby-launcher" 289 | 290 | Dir.mktmpdir("jruby-launcher-") do |tmpdir| 291 | Dir.chdir(tmpdir) do 292 | sh "gem fetch jruby-launcher --platform java --version #{version}" 293 | sh "gem unpack jruby-launcher-#{version}-java.gem" 294 | 295 | build_command = [ 296 | "make", 297 | "mkdir -p #{prefix}", 298 | "cp jruby #{prefix}" 299 | ].join(" && ") 300 | 301 | sh "vulcan build -v -o #{name}.tgz --source #{name} --prefix #{prefix} --command=\"#{build_command}\"" 302 | s3_upload(tmpdir, name) 303 | end 304 | end 305 | 306 | end 307 | 308 | desc "generate ruby versions manifest" 309 | task "ruby:manifest" do 310 | require 'rexml/document' 311 | require 'yaml' 312 | 313 | document = REXML::Document.new(`curl https://#{S3_BUCKET_NAME}.s3.amazonaws.com`) 314 | rubies = document.elements.to_a("//Contents/Key").map {|node| node.text }.select {|text| text.match(/^(ruby|rbx|jruby)-\\\\d+\\\\.\\\\d+\\\\.\\\\d+(-p\\\\d+)?/) } 315 | 316 | Dir.mktmpdir("ruby_versions-") do |tmpdir| 317 | name = 'ruby_versions.yml' 318 | File.open(name, 'w') {|file| file.puts(rubies.to_yaml) } 319 | sh("#{s3_tools_dir}/s3 put #{S3_BUCKET_NAME} #{name} #{name}") 320 | end 321 | end 322 | 323 | desc "install libffi" 324 | task "libffi:install", :version do |t, args| 325 | version = args[:version] 326 | name = "libffi-#{version}" 327 | prefix = "/app/vendor/#{name}" 328 | Dir.mktmpdir("libffi-") do |tmpdir| 329 | Dir.chdir(tmpdir) do |dir| 330 | FileUtils.rm_rf("#{tmpdir}/*") 331 | 332 | sh "curl ftp://sourceware.org/pub/libffi/libffi-#{version}.tar.gz -s -o - | tar vzxf -" 333 | 334 | build_command = [ 335 | "env CFLAGS=-fPIC ./configure --enable-static --disable-shared --prefix=#{prefix}", 336 | "make", 337 | "make install", 338 | "mv #{prefix}/lib/#{name}/include #{prefix}", 339 | "rm -rf #{prefix}/lib/#{name}" 340 | ].join(" && ") 341 | 342 | sh "vulcan build -v -o #{name}.tgz --source #{name} --prefix=#{prefix} --command=\"#{build_command}\"" 343 | s3_upload(tmpdir, name) 344 | end 345 | end 346 | end 347 | 348 | begin 349 | require 'rspec/core/rake_task' 350 | 351 | desc "Run specs" 352 | RSpec::Core::RakeTask.new(:spec) do |t| 353 | t.rspec_opts = %w(-fs --color) 354 | #t.ruby_opts = %w(-w) 355 | end 356 | task :default => :spec 357 | rescue LoadError => e 358 | end 359 | -------------------------------------------------------------------------------- /lib/language_pack/ruby.rb: -------------------------------------------------------------------------------- 1 | require "tmpdir" 2 | require "rubygems" 3 | require "language_pack" 4 | require "language_pack/base" 5 | require "language_pack/bundler_lockfile" 6 | 7 | # base Ruby Language Pack. This is for any base ruby app. 8 | class LanguagePack::Ruby < LanguagePack::Base 9 | include LanguagePack::BundlerLockfile 10 | extend LanguagePack::BundlerLockfile 11 | 12 | BUILDPACK_VERSION = "v61" 13 | LIBYAML_VERSION = "0.1.4" 14 | LIBYAML_PATH = "libyaml-#{LIBYAML_VERSION}" 15 | BUNDLER_VERSION = "1.3.2" 16 | BUNDLER_GEM_PATH = "bundler-#{BUNDLER_VERSION}" 17 | NODE_VERSION = "0.4.7" 18 | NODE_JS_BINARY_PATH = "node-#{NODE_VERSION}" 19 | JVM_BASE_URL = "http://heroku-jdk.s3.amazonaws.com" 20 | JVM_VERSION = "openjdk7-latest" 21 | SQLITE_VERSION = "3071700" 22 | SQLITE_PATH = "sqlite-autoconf-#{SQLITE_VERSION}" 23 | 24 | MY_VENDOR_URL = "https://s3.amazonaws.com/prod-audi-leasing-us" 25 | 26 | # detects if this is a valid Ruby app 27 | # @return [Boolean] true if it's a Ruby app 28 | def self.use? 29 | File.exist?("Gemfile") 30 | end 31 | 32 | def self.lockfile_parser 33 | require "bundler" 34 | Bundler::LockfileParser.new(File.read("Gemfile.lock")) 35 | end 36 | 37 | def self.gem_version(name) 38 | gem_version = nil 39 | bootstrap_bundler do |bundler_path| 40 | $: << "#{bundler_path}/gems/bundler-#{LanguagePack::Ruby::BUNDLER_VERSION}/lib" 41 | gem = lockfile_parser.specs.detect { |gem| gem.name == name } 42 | gem_version = gem.version if gem 43 | end 44 | 45 | gem_version 46 | end 47 | 48 | def name 49 | "Ruby" 50 | end 51 | 52 | def default_addons 53 | add_dev_database_addon 54 | end 55 | 56 | def default_config_vars 57 | vars = { 58 | "LANG" => "en_US.UTF-8", 59 | "PATH" => default_path, 60 | "GEM_PATH" => slug_vendor_base, 61 | } 62 | 63 | ruby_version_jruby? ? vars.merge({ 64 | "JAVA_OPTS" => default_java_opts, 65 | "JRUBY_OPTS" => default_jruby_opts, 66 | "JAVA_TOOL_OPTIONS" => default_java_tool_options 67 | }) : vars 68 | end 69 | 70 | def default_process_types 71 | { 72 | "rake" => "bundle exec rake", 73 | "console" => "bundle exec irb" 74 | } 75 | end 76 | 77 | def compile 78 | Dir.chdir(build_path) 79 | remove_vendor_bundle 80 | install_ruby 81 | install_jvm 82 | setup_language_pack_environment 83 | setup_profiled 84 | allow_git do 85 | install_language_pack_gems 86 | build_bundler 87 | create_database_yml 88 | install_binaries 89 | run_assets_precompile_rake_task 90 | end 91 | end 92 | 93 | private 94 | 95 | # the base PATH environment variable to be used 96 | # @return [String] the resulting PATH 97 | def default_path 98 | "bin:#{slug_vendor_base}/bin:/usr/local/bin:/usr/bin:/bin" 99 | end 100 | 101 | # the relative path to the bundler directory of gems 102 | # @return [String] resulting path 103 | def slug_vendor_base 104 | if @slug_vendor_base 105 | @slug_vendor_base 106 | elsif @ruby_version == "ruby-1.8.7" 107 | @slug_vendor_base = "vendor/bundle/1.8" 108 | else 109 | @slug_vendor_base = run(%q(ruby -e "require 'rbconfig';puts \"vendor/bundle/#{RUBY_ENGINE}/#{RbConfig::CONFIG['ruby_version']}\"")).chomp 110 | end 111 | end 112 | 113 | # the relative path to the vendored ruby directory 114 | # @return [String] resulting path 115 | def slug_vendor_ruby 116 | "vendor/#{ruby_version}" 117 | end 118 | 119 | # the relative path to the vendored jvm 120 | # @return [String] resulting path 121 | def slug_vendor_jvm 122 | "vendor/jvm" 123 | end 124 | 125 | # the absolute path of the build ruby to use during the buildpack 126 | # @return [String] resulting path 127 | def build_ruby_path 128 | "/tmp/#{ruby_version}" 129 | end 130 | 131 | # fetch the ruby version from bundler 132 | # @return [String, nil] returns the ruby version if detected or nil if none is detected 133 | def ruby_version 134 | return @ruby_version if @ruby_version_run 135 | 136 | @ruby_version_run = true 137 | 138 | bootstrap_bundler do |bundler_path| 139 | old_system_path = "/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" 140 | @ruby_version = run_stdout("env PATH=#{old_system_path}:#{bundler_path}/bin GEM_PATH=#{bundler_path} bundle platform --ruby").chomp 141 | end 142 | 143 | if @ruby_version == "No ruby version specified" && ENV['RUBY_VERSION'] 144 | # for backwards compatibility. 145 | # this will go away in the future 146 | @ruby_version = ENV['RUBY_VERSION'] 147 | @ruby_version_env_var = true 148 | elsif @ruby_version == "No ruby version specified" 149 | @ruby_version = nil 150 | else 151 | @ruby_version = @ruby_version.sub('(', '').sub(')', '').split.join('-') 152 | @ruby_version_env_var = false 153 | end 154 | 155 | @ruby_version 156 | end 157 | 158 | # determine if we're using rbx 159 | # @return [Boolean] true if we are and false if we aren't 160 | def ruby_version_rbx? 161 | ruby_version ? ruby_version.match(/rbx-/) : false 162 | end 163 | 164 | # determine if we're using jruby 165 | # @return [Boolean] true if we are and false if we aren't 166 | def ruby_version_jruby? 167 | @ruby_version_jruby ||= ruby_version ? ruby_version.match(/jruby-/) : false 168 | end 169 | 170 | # default JAVA_OPTS 171 | # return [String] string of JAVA_OPTS 172 | def default_java_opts 173 | "-Xmx384m -Xss512k -XX:+UseCompressedOops -Dfile.encoding=UTF-8" 174 | end 175 | 176 | # default JRUBY_OPTS 177 | # return [String] string of JRUBY_OPTS 178 | def default_jruby_opts 179 | "-Xcompile.invokedynamic=true" 180 | end 181 | 182 | # default JAVA_TOOL_OPTIONS 183 | # return [String] string of JAVA_TOOL_OPTIONS 184 | def default_java_tool_options 185 | "-Djava.rmi.server.useCodebaseOnly=true" 186 | end 187 | 188 | # list the available valid ruby versions 189 | # @note the value is memoized 190 | # @return [Array] list of Strings of the ruby versions available 191 | def ruby_versions 192 | return @ruby_versions if @ruby_versions 193 | 194 | Dir.mktmpdir("ruby_versions-") do |tmpdir| 195 | Dir.chdir(tmpdir) do 196 | run("curl -O #{VENDOR_URL}/ruby_versions.yml") 197 | @ruby_versions = YAML::load_file("ruby_versions.yml") 198 | end 199 | end 200 | 201 | @ruby_versions 202 | end 203 | 204 | # sets up the environment variables for the build process 205 | def setup_language_pack_environment 206 | setup_ruby_install_env 207 | 208 | config_vars = default_config_vars.each do |key, value| 209 | ENV[key] ||= value 210 | end 211 | ENV["GEM_HOME"] = slug_vendor_base 212 | ENV["PATH"] = "#{ruby_install_binstub_path}:#{config_vars["PATH"]}" 213 | end 214 | 215 | # sets up the profile.d script for this buildpack 216 | def setup_profiled 217 | set_env_override "GEM_PATH", "$HOME/#{slug_vendor_base}:$GEM_PATH" 218 | set_env_default "LANG", "en_US.UTF-8" 219 | set_env_override "PATH", "$HOME/bin:$HOME/#{slug_vendor_base}/bin:$PATH" 220 | 221 | if ruby_version_jruby? 222 | set_env_default "JAVA_OPTS", default_java_opts 223 | set_env_default "JRUBY_OPTS", default_jruby_opts 224 | set_env_default "JAVA_TOOL_OPTIONS", default_java_tool_options 225 | end 226 | end 227 | 228 | # determines if a build ruby is required 229 | # @return [Boolean] true if a build ruby is required 230 | def build_ruby? 231 | @build_ruby ||= !ruby_version_rbx? && !ruby_version_jruby? && !%w{ruby-1.9.3 ruby-2.0.0}.include?(ruby_version) 232 | end 233 | 234 | # install the vendored ruby 235 | # @return [Boolean] true if it installs the vendored ruby and false otherwise 236 | def install_ruby 237 | return false unless ruby_version 238 | 239 | invalid_ruby_version_message = < true 360 | end 361 | 362 | # install libyaml into the LP to be referenced for psych compilation 363 | # @param [String] tmpdir to store the libyaml files 364 | def install_libyaml(dir) 365 | FileUtils.mkdir_p dir 366 | Dir.chdir(dir) do |dir| 367 | run("curl #{VENDOR_URL}/#{LIBYAML_PATH}.tgz -s -o - | tar xzf -") 368 | end 369 | end 370 | 371 | # install sqlite into the LP to be referenced for psych compilation 372 | # @param [String] tmpdir to store the sqlite files 373 | def install_sqlite(dir) 374 | FileUtils.mkdir_p dir 375 | Dir.chdir(dir) do |dir| 376 | puts "curl #{MY_VENDOR_URL}/#{SQLITE_PATH}.tar.gz -s -o - | tar xzf - 2>&1" 377 | run("curl #{MY_VENDOR_URL}/#{SQLITE_PATH}.tar.gz -s -o - | tar xzf - 2>&1") 378 | end 379 | end 380 | 381 | # remove `vendor/bundle` that comes from the git repo 382 | # in case there are native ext. 383 | # users should be using `bundle pack` instead. 384 | # https://github.com/heroku/heroku-buildpack-ruby/issues/21 385 | def remove_vendor_bundle 386 | if File.exists?("vendor/bundle") 387 | topic "WARNING: Removing `vendor/bundle`." 388 | puts "Checking in `vendor/bundle` is not supported. Please remove this directory" 389 | puts "and add it to your .gitignore. To vendor your gems with Bundler, use" 390 | puts "`bundle pack` instead." 391 | FileUtils.rm_rf("vendor/bundle") 392 | end 393 | end 394 | 395 | # runs bundler to install the dependencies 396 | def build_bundler 397 | log("bundle") do 398 | bundle_without = ENV["BUNDLE_WITHOUT"] || "development:test" 399 | bundle_command = "bundle install --without #{bundle_without} --path vendor/bundle --binstubs vendor/bundle/bin" 400 | 401 | unless File.exist?("Gemfile.lock") 402 | error "Gemfile.lock is required. Please run \"bundle install\" locally\nand commit your Gemfile.lock." 403 | end 404 | 405 | if has_windows_gemfile_lock? 406 | topic "WARNING: Removing `Gemfile.lock` because it was generated on Windows." 407 | puts "Bundler will do a full resolve so native gems are handled properly." 408 | puts "This may result in unexpected gem versions being used in your app." 409 | 410 | log("bundle", "has_windows_gemfile_lock") 411 | File.unlink("Gemfile.lock") 412 | else 413 | # using --deployment is preferred if we can 414 | bundle_command += " --deployment" 415 | cache_load ".bundle" 416 | end 417 | 418 | version = run_stdout("bundle version").strip 419 | topic("Installing dependencies using #{version}") 420 | 421 | load_bundler_cache 422 | 423 | bundler_output = "" 424 | Dir.mktmpdir("yamltag-") do |tmpdir| 425 | libyaml_dir = "#{tmpdir}/#{LIBYAML_PATH}" 426 | sqlite_dir = "#{tmpdir}/#{SQLITE_PATH}" 427 | puts "Installing libyaml to #{libyaml_dir}" 428 | install_libyaml(libyaml_dir) 429 | 430 | puts "Installing SQLite to #{sqlite_dir}" 431 | install_sqlite(sqlite_dir) 432 | 433 | if File.exist? "#{sqlite_dir}/#{SQLITE_PATH}/" 434 | puts "folder exists" 435 | else 436 | puts "folder missing" 437 | end 438 | 439 | if File.exist? "#{sqlite_dir}/#{SQLITE_PATH}/sqlite3.h" 440 | puts "sqlite3.h exists" 441 | else 442 | puts "sqlite3.h missing" 443 | end 444 | 445 | # need to setup compile environment for the psych gem 446 | yaml_include = File.expand_path("#{libyaml_dir}/include") 447 | yaml_lib = File.expand_path("#{libyaml_dir}/lib") 448 | 449 | sqlite_include = File.expand_path("#{sqlite_dir}/include") 450 | sqlite_lib = File.expand_path("#{sqlite_dir}/lib") 451 | 452 | 453 | pwd = run("pwd").chomp 454 | bundler_path = "#{pwd}/#{slug_vendor_base}/gems/#{BUNDLER_GEM_PATH}/lib" 455 | # we need to set BUNDLE_CONFIG and BUNDLE_GEMFILE for 456 | # codon since it uses bundler. 457 | 458 | env_vars = "env BUNDLE_GEMFILE=#{pwd}/Gemfile BUNDLE_CONFIG=#{pwd}/.bundle/config CPATH=#{yaml_include}:#{sqlite_include}:$CPATH CPPATH=#{yaml_include}:#{sqlite_include}:$CPPATH LIBRARY_PATH=#{yaml_lib}:#{sqlite_lib}:$LIBRARY_PATH RUBYOPT=\"#{syck_hack}\"" 459 | env_vars += " BUNDLER_LIB_PATH=#{bundler_path}" if ruby_version == "ruby-1.8.7" 460 | 461 | sqlite_command = "gem install sqlite3 -- --with-sqlite3-dir=#{sqlite_dir}/#{SQLITE_PATH}/" 462 | puts "Running: #{sqlite_command}" 463 | bundler_output << pipe(sqlite_command) 464 | 465 | puts "Running: #{bundle_command}" 466 | bundler_output << pipe("#{env_vars} #{bundle_command} --no-clean 2>&1") 467 | end 468 | 469 | if $?.success? 470 | log "bundle", :status => "success" 471 | puts "Cleaning up the bundler cache." 472 | pipe "bundle clean 2> /dev/null" 473 | cache_store ".bundle" 474 | cache_store "vendor/bundle" 475 | 476 | # Keep gem cache out of the slug 477 | FileUtils.rm_rf("#{slug_vendor_base}/cache") 478 | 479 | # symlink binstubs 480 | bin_dir = "bin" 481 | FileUtils.mkdir_p bin_dir 482 | Dir["#{slug_vendor_base}/bin/*"].each do |bin| 483 | run("ln -s ../#{bin} #{bin_dir}") unless File.exist?("#{bin_dir}/#{bin}") 484 | end 485 | else 486 | log "bundle", :status => "failure" 487 | error_message = "Failed to install gems via Bundler." 488 | # if bundler_output.match(/Installing sqlite3 \([\w.]+\) with native extensions\s+Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension./) 489 | # error_message += < 563 | 564 | <%= ENV["RAILS_ENV"] || ENV["RACK_ENV"] %>: 565 | <%= attribute "adapter", adapter %> 566 | <%= attribute "database", database %> 567 | <%= attribute "username", username %> 568 | <%= attribute "password", password, true %> 569 | <%= attribute "host", host %> 570 | <%= attribute "port", port %> 571 | 572 | <% params.each do |key, value| %> 573 | <%= key %>: <%= value.first %> 574 | <% end %> 575 | DATABASE_YML 576 | end 577 | end 578 | end 579 | 580 | # add bundler to the load path 581 | # @note it sets a flag, so the path can only be loaded once 582 | def add_bundler_to_load_path 583 | return if @bundler_loadpath 584 | $: << File.expand_path(Dir["#{slug_vendor_base}/gems/bundler*/lib"].first) 585 | @bundler_loadpath = true 586 | end 587 | 588 | # detects whether the Gemfile.lock contains the Windows platform 589 | # @return [Boolean] true if the Gemfile.lock was created on Windows 590 | def has_windows_gemfile_lock? 591 | lockfile_parser.platforms.detect do |platform| 592 | /mingw|mswin/.match(platform.os) if platform.is_a?(Gem::Platform) 593 | end 594 | end 595 | 596 | # detects if a gem is in the bundle. 597 | # @param [String] name of the gem in question 598 | # @return [String, nil] if it finds the gem, it will return the line from bundle show or nil if nothing is found. 599 | def gem_is_bundled?(gem) 600 | @bundler_gems ||= lockfile_parser.specs.map(&:name) 601 | @bundler_gems.include?(gem) 602 | end 603 | 604 | # setup the lockfile parser 605 | # @return [Bundler::LockfileParser] a Bundler::LockfileParser 606 | def lockfile_parser 607 | add_bundler_to_load_path 608 | @lockfile_parser ||= LanguagePack::Ruby.lockfile_parser 609 | end 610 | 611 | # detects if a rake task is defined in the app 612 | # @param [String] the task in question 613 | # @return [Boolean] true if the rake task is defined in the app 614 | def rake_task_defined?(task) 615 | run("env PATH=$PATH bundle exec rake #{task} --dry-run") && $?.success? 616 | end 617 | 618 | # executes the block with GIT_DIR environment variable removed since it can mess with the current working directory git thinks it's in 619 | # @param [block] block to be executed in the GIT_DIR free context 620 | def allow_git(&blk) 621 | git_dir = ENV.delete("GIT_DIR") # can mess with bundler 622 | blk.call 623 | ENV["GIT_DIR"] = git_dir 624 | end 625 | 626 | # decides if we need to enable the dev database addon 627 | # @return [Array] the database addon if the pg gem is detected or an empty Array if it isn't. 628 | def add_dev_database_addon 629 | gem_is_bundled?("pg") ? ['heroku-postgresql:dev'] : [] 630 | end 631 | 632 | # decides if we need to install the node.js binary 633 | # @note execjs will blow up if no JS RUNTIME is detected and is loaded. 634 | # @return [Array] the node.js binary path if we need it or an empty Array 635 | def add_node_js_binary 636 | gem_is_bundled?('execjs') ? [NODE_JS_BINARY_PATH] : [] 637 | end 638 | 639 | def run_assets_precompile_rake_task 640 | if rake_task_defined?("assets:precompile") 641 | require 'benchmark' 642 | 643 | topic "Running: rake assets:precompile" 644 | time = Benchmark.realtime { pipe("env PATH=$PATH:bin bundle exec rake assets:precompile 2>&1") } 645 | if $?.success? 646 | puts "Asset precompilation completed (#{"%.2f" % time}s)" 647 | end 648 | end 649 | end 650 | 651 | def bundler_cache 652 | "vendor/bundle" 653 | end 654 | 655 | def load_bundler_cache 656 | cache_load "vendor" 657 | 658 | full_ruby_version = run_stdout(%q(ruby -v)).chomp 659 | rubygems_version = run_stdout(%q(gem -v)).chomp 660 | heroku_metadata = "vendor/heroku" 661 | old_rubygems_version = nil 662 | ruby_version_cache = "#{heroku_metadata}/ruby_version" 663 | buildpack_version_cache = "#{heroku_metadata}/buildpack_version" 664 | bundler_version_cache = "#{heroku_metadata}/bundler_version" 665 | rubygems_version_cache = "#{heroku_metadata}/rubygems_version" 666 | 667 | old_rubygems_version = File.read(rubygems_version_cache).chomp if File.exists?(rubygems_version_cache) 668 | 669 | # fix bug from v37 deploy 670 | if File.exists?("vendor/ruby_version") 671 | puts "Broken cache detected. Purging build cache." 672 | cache_clear("vendor") 673 | FileUtils.rm_rf("vendor/ruby_version") 674 | purge_bundler_cache 675 | # fix bug introduced in v38 676 | elsif !File.exists?(buildpack_version_cache) && File.exists?(ruby_version_cache) 677 | puts "Broken cache detected. Purging build cache." 678 | purge_bundler_cache 679 | elsif cache_exists?(bundler_cache) && File.exists?(ruby_version_cache) && full_ruby_version != File.read(ruby_version_cache).chomp 680 | puts "Ruby version change detected. Clearing bundler cache." 681 | puts "Old: #{File.read(ruby_version_cache).chomp}" 682 | puts "New: #{full_ruby_version}" 683 | purge_bundler_cache 684 | end 685 | 686 | # fix git gemspec bug from Bundler 1.3.0+ upgrade 687 | if File.exists?(bundler_cache) && !File.exists?(bundler_version_cache) && !run("find vendor/bundle/*/*/bundler/gems/*/ -name *.gemspec").include?("No such file or directory") 688 | puts "Old bundler cache detected. Clearing bundler cache." 689 | purge_bundler_cache 690 | end 691 | 692 | # fix for https://github.com/heroku/heroku-buildpack-ruby/issues/86 693 | if (!File.exists?(rubygems_version_cache) || 694 | (old_rubygems_version == "2.0.0" && old_rubygems_version != rubygems_version)) && 695 | File.exists?(ruby_version_cache) && File.read(ruby_version_cache).chomp.include?("ruby 2.0.0p0") 696 | puts "Updating to rubygems #{rubygems_version}. Clearing bundler cache." 697 | purge_bundler_cache 698 | end 699 | 700 | FileUtils.mkdir_p(heroku_metadata) 701 | File.open(ruby_version_cache, 'w') do |file| 702 | file.puts full_ruby_version 703 | end 704 | File.open(buildpack_version_cache, 'w') do |file| 705 | file.puts BUILDPACK_VERSION 706 | end 707 | File.open(bundler_version_cache, 'w') do |file| 708 | file.puts BUNDLER_VERSION 709 | end 710 | File.open(rubygems_version_cache, 'w') do |file| 711 | file.puts rubygems_version 712 | end 713 | cache_store heroku_metadata 714 | end 715 | 716 | def purge_bundler_cache 717 | FileUtils.rm_rf(bundler_cache) 718 | cache_clear bundler_cache 719 | # need to reinstall language pack gems 720 | install_language_pack_gems 721 | end 722 | end 723 | --------------------------------------------------------------------------------