-
Notifications
You must be signed in to change notification settings - Fork 19
/
base.rb
226 lines (189 loc) · 5.66 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
require "language_pack"
require "pathname"
require "yaml"
require "digest/sha1"
Encoding.default_external = Encoding::UTF_8 if defined?(Encoding)
# abstract class that all the Ruby based Language Packs inherit from
class LanguagePack::Base
VENDOR_URL = "https://s3.amazonaws.com/heroku-buildpack-ruby"
attr_reader :build_path, :cache_path
# changes directory to the build_path
# @param [String] the path of the build dir
# @param [String] the path of the cache dir
def initialize(build_path, cache_path=nil)
@build_path = build_path
@cache_path = cache_path
@id = Digest::SHA1.hexdigest("#{Time.now.to_f}-#{rand(1000000)}")[0..10]
Dir.chdir build_path
end
def self.===(build_path)
raise "must subclass"
end
# name of the Language Pack
# @return [String] the result
def name
raise "must subclass"
end
# list of default addons to install
def default_addons
raise "must subclass"
end
# config vars to be set on first push.
# @return [Hash] the result
# @not: this is only set the first time an app is pushed to.
def default_config_vars
raise "must subclass"
end
# process types to provide for the app
# Ex. for rails we provide a web process
# @return [Hash] the result
def default_process_types
raise "must subclass"
end
# this is called to build the slug
def compile
end
# collection of values passed for a release
# @return [String] in YAML format of the result
def release
setup_language_pack_environment
{
"addons" => default_addons,
"config_vars" => default_config_vars,
"default_process_types" => default_process_types
}.to_yaml
end
# log output
# Ex. log "some_message", "here", :someattr="value"
def log(*args)
args.concat [:id => @id]
args.concat [:framework => self.class.to_s.split("::").last.downcase]
start = Time.now.to_f
log_internal args, :start => start
if block_given?
begin
ret = yield
finish = Time.now.to_f
log_internal args, :status => "complete", :finish => finish, :elapsed => (finish - start)
return ret
rescue StandardError => ex
finish = Time.now.to_f
log_internal args, :status => "error", :finish => finish, :elapsed => (finish - start), :message => ex.message
raise ex
end
end
end
private ##################################
# sets up the environment variables for the build process
def setup_language_pack_environment
end
def add_to_profiled(string)
FileUtils.mkdir_p "#{build_path}/.profile.d"
File.open("#{build_path}/.profile.d/ruby.sh", "a") do |file|
file.puts string
end
end
def set_env_default(key, val)
add_to_profiled "export #{key}=${#{key}:-#{val}}"
end
def set_env_override(key, val)
add_to_profiled %{export #{key}="#{val.gsub('"','\"')}"}
end
def log_internal(*args)
message = build_log_message(args)
%x{ logger -p user.notice -t "slugc[$$]" "buildpack-ruby #{message}" }
end
def build_log_message(args)
args.map do |arg|
case arg
when Float then "%0.2f" % arg
when Array then build_log_message(arg)
when Hash then arg.map { |k,v| "#{k}=#{build_log_message([v])}" }.join(" ")
else arg
end
end.join(" ")
end
# display error message and stop the build process
# @param [String] error message
def error(message)
Kernel.puts " !"
message.split("\n").each do |line|
Kernel.puts " ! #{line.strip}"
end
Kernel.puts " !"
log "exit", :error => message
exit 1
end
# run a shell comannd and pipe stderr to stdout
# @param [String] command to be run
# @return [String] output of stdout and stderr
def run(command)
%x{ #{command} 2>&1 }
end
# run a shell command and pipe stderr to /dev/null
# @param [String] command to be run
# @return [String] output of stdout
def run_stdout(command)
%x{ #{command} 2>/dev/null }
end
# run a shell command and stream the ouput
# @param [String] command to be run
def pipe(command)
output = ""
IO.popen(command) do |io|
until io.eof?
buffer = io.gets
output << buffer
puts buffer
end
end
output
end
# display a topic message
# (denoted by ----->)
# @param [String] topic message to be displayed
def topic(message)
Kernel.puts "-----> #{message}"
$stdout.flush
end
# display a message in line
# (indented by 6 spaces)
# @param [String] message to be displayed
def puts(message)
message.split("\n").each do |line|
super " #{line.strip}"
end
$stdout.flush
end
# create a Pathname of the cache dir
# @return [Pathname] the cache dir
def cache_base
Pathname.new(cache_path)
end
# removes the the specified
# @param [String] relative path from the cache_base
def cache_clear(path)
target = (cache_base + path)
target.exist? && target.rmtree
end
# write cache contents
# @param [String] path of contents to store. it will be stored using this a relative path from the cache_base.
# @param [Boolean] defaults to true. if set to true, the cache store directory will be cleared before writing to it.
def cache_store(path, clear_first=true)
cache_clear(path) if clear_first
cache_copy path, (cache_base + path)
end
# load cache contents
# @param [String] relative path of the cache contents
def cache_load(path)
cache_copy (cache_base + path), path
end
# copy cache contents
# @param [String] source directory
# @param [String] destination directory
def cache_copy(from, to)
return false unless File.exist?(from)
FileUtils.mkdir_p File.dirname(to)
system("cp -a #{from}/. #{to}")
end
end