-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
base.rb
297 lines (252 loc) · 8.03 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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
require "language_pack"
require "pathname"
require "yaml"
require "digest/sha1"
require "language_pack/shell_helpers"
require "language_pack/cache"
require "language_pack/helpers/bundler_cache"
require "language_pack/helpers/layer"
require "language_pack/metadata"
require "language_pack/fetcher"
require "language_pack/instrument"
Encoding.default_external = Encoding::UTF_8 if defined?(Encoding)
ENV["BPLOG_PREFIX"] = "buildpack.ruby"
# abstract class that all the Ruby based Language Packs inherit from
class LanguagePack::Base
include LanguagePack::ShellHelpers
extend LanguagePack::ShellHelpers
VENDOR_URL = ENV['BUILDPACK_VENDOR_URL'] || "https://s3-external-1.amazonaws.com/heroku-buildpack-ruby"
DEFAULT_LEGACY_STACK = "cedar"
ROOT_DIR = File.expand_path("../../..", __FILE__)
attr_reader :build_path, :cache, :stack
# changes directory to the build_path
# @param [String] the path of the build dir
# @param [String] the path of the cache dir this is nil during detect and release
def initialize(build_path, cache_path = nil, layer_dir=nil)
self.class.instrument "base.initialize" do
@build_path = build_path
@stack = ENV.fetch("STACK")
@cache = LanguagePack::Cache.new(cache_path)
@metadata = LanguagePack::Metadata.new(@cache)
@bundler_cache = LanguagePack::BundlerCache.new(@cache, @stack)
@id = Digest::SHA1.hexdigest("#{Time.now.to_f}-#{rand(1000000)}")[0..10]
@fetchers = {:buildpack => LanguagePack::Fetcher.new(VENDOR_URL) }
@layer_dir = layer_dir
Dir.chdir build_path
end
end
def instrument(*args, &block)
self.class.instrument(*args, &block)
end
def self.instrument(*args, &block)
LanguagePack::Instrument.instrument(*args, &block)
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
write_release_yaml
instrument 'base.compile' do
Kernel.puts ""
warnings.each do |warning|
Kernel.puts "\e[1m\e[33m###### WARNING:\e[0m"# Bold yellow
Kernel.puts ""
puts warning
Kernel.puts ""
end
if deprecations.any?
topic "DEPRECATIONS:"
puts @deprecations.join("\n")
end
Kernel.puts ""
end
mcount "success"
end
def build
write_release_toml
instrument 'base.compile' do
Kernel.puts ""
warnings.each do |warning|
Kernel.puts "\e[1m\e[33m###### WARNING:\e[0m"# Bold yellow
Kernel.puts ""
puts warning
Kernel.puts ""
Kernel.puts ""
end
if deprecations.any?
topic "DEPRECATIONS:"
puts @deprecations.join("\n")
end
Kernel.puts ""
end
mcount "success"
end
def build_release
release = {}
release["addons"] = default_addons
release["config_vars"] = default_config_vars
release["default_process_types"] = default_process_types
release
end
def write_release_toml
release = build_release
layer = LanguagePack::Helpers::Layer.new(@layer_dir, "env", launch: true)
FileUtils.mkdir_p("#{layer.path}/env.launch")
release["config_vars"].each do |key, value|
File.open("#{layer.path}/env.launch/#{key.upcase}.override", 'w') do |f|
f.write(value)
end
end
release_toml = release["default_process_types"].map do |type, command|
<<PROCESSES
[[processes]]
type = "#{type}"
command = "#{command}"
PROCESSES
end
File.open("#{@layer_dir}/launch.toml", 'a') do |f|
f.write(release_toml.join("\n"))
end
end
def write_release_yaml
release = build_release
FileUtils.mkdir("tmp") unless File.exists?("tmp")
File.open("tmp/heroku-buildpack-release-step.yml", 'w') do |f|
f.write(release.to_yaml)
end
warn_webserver
end
# write out the release. Pick v2a or v3 release format
def write_release
@layer_dir ? write_release_toml : write_release_yaml
end
def warn_webserver
return if File.exist?("Procfile")
msg = "No Procfile detected, using the default web server.\n"
msg << "We recommend explicitly declaring how to boot your server process via a Procfile.\n"
msg << "https://devcenter.heroku.com/articles/ruby-default-web-server"
warn msg
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
message = Shellwords.escape(ex.message)
log_internal args, :status => "error", :finish => finish, :elapsed => (finish - start), :message => 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)
profiled_path = @layer_dir ? "#{@layer_dir}/ruby/profile.d/" : "#{build_path}/.profile.d/"
FileUtils.mkdir_p profiled_path
File.open("#{profiled_path}/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 add_to_export(string)
export = File.join(ROOT_DIR, "export")
File.open(export, "a") do |file|
file.puts string
end
end
# option can be :path, :default, :override
# https://github.com/buildpacks/spec/blob/366ac1aa0be59d11010cc21aa06c16d81d8d43e7/buildpack.md#environment-variable-modification-rules
def export(key, val, layer: nil, option: nil)
if layer
# don't replace if the key is already set
return if ENV[key] && option == :default
filename =
if option.nil? || option == :path
key
elsif option == :default
"#{key}.override"
else
"#{key}.#{option}"
end
FileUtils.mkdir_p("#{layer.path}/env.build")
File.open("#{layer.path}/env.build/#{filename}", "w") do |f|
f.write(val)
end
else
string =
if option == :default
%{export #{key}="${#{key}:-#{val}}"}
elsif option == :path
%{export #{key}="#{val}:$#{key}"}
else
%{export #{key}="#{val.gsub('"','\"')}"}
end
export = File.join(ROOT_DIR, "export")
File.open(export, "a") do |file|
file.puts string
end
end
end
def set_export_default(key, val, layer = nil)
export key, val, layer: layer, option: :default
end
def set_export_override(key, val, layer = nil)
export key, val, layer: layer, option: :override
end
def set_export_path(key, val, layer = nil)
export key, val, layer: layer, option: :path
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
end