rails_build 1.2.0 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE +1 -0
- data/README.md +71 -127
- data/Rakefile +437 -22
- data/bin/rails_build +387 -365
- data/lib/rails_build/_lib.rb +87 -0
- data/lib/rails_build.rb +188 -17
- data/rails_build.gemspec +40 -0
- metadata +31 -110
- data/MIT-LICENSE +0 -20
- data/app/assets/config/rails_build_manifest.js +0 -2
- data/app/assets/javascripts/rails_build/application.js +0 -13
- data/app/assets/stylesheets/rails_build/application.css +0 -32
- data/app/controllers/rails_build/application_controller.rb +0 -12
- data/app/helpers/rails_build/application_helper.rb +0 -4
- data/app/jobs/rails_build/application_job.rb +0 -4
- data/app/mailers/rails_build/application_mailer.rb +0 -6
- data/app/models/rails_build/application_record.rb +0 -5
- data/app/views/layouts/rails_build/application.html.erb +0 -14
- data/app/views/rails_build/application/index.html.erb +0 -20
- data/bin/rails +0 -13
- data/config/routes.rb +0 -10
- data/lib/rails_build/engine.rb +0 -50
- data/lib/rails_build/version.rb +0 -3
- data/lib/tasks/rails_build_tasks.rake +0 -31
data/bin/rails_build
CHANGED
@@ -1,34 +1,25 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# file : ./bin/rails_build
|
4
|
-
|
5
|
-
END{
|
6
|
-
|
7
|
-
RailsBuild::CLI.build!
|
8
|
-
|
9
|
-
}
|
10
|
-
|
2
|
+
# encoding: utf-8
|
11
3
|
|
12
4
|
module RailsBuild
|
13
5
|
class CLI
|
14
6
|
def CLI.usage
|
15
|
-
|
7
|
+
<<~__
|
16
8
|
NAME
|
17
9
|
rails_build
|
18
10
|
|
19
11
|
SYNOPSIS
|
20
|
-
a small, simple, bullet proof, and
|
12
|
+
a small, simple, bullet proof, and fast enough static site generator built on top of the rails you already know and love
|
21
13
|
|
22
14
|
USAGE
|
23
|
-
rails_build
|
15
|
+
rails_build *(options)
|
24
16
|
|
25
17
|
options:
|
26
18
|
--help, -h : this message
|
27
|
-
--
|
28
|
-
--parallel,--p : how many requests to make in parallel, default=n_cpus
|
19
|
+
--init, -i : initialize ./config/rails_build.rb
|
20
|
+
--parallel,--p : how many requests to make in parallel, default=n_cpus-1
|
29
21
|
--env,--e : speciify the RAILS_ENV, default=production
|
30
22
|
--url, -u : provide the url of the build server, do *not* start separate one
|
31
|
-
--server, -s : passenger | puma, default=passenger
|
32
23
|
--log, -l : specify the logfile, default=STDERR
|
33
24
|
--verbose, -v : turn on verbose logging
|
34
25
|
__
|
@@ -36,27 +27,49 @@ module RailsBuild
|
|
36
27
|
|
37
28
|
def CLI.opts
|
38
29
|
GetoptLong.new(
|
39
|
-
[ '--help' , '-h' , GetoptLong::NO_ARGUMENT ]
|
40
|
-
[ '--
|
41
|
-
[ '--
|
42
|
-
[ '--
|
43
|
-
[ '--
|
44
|
-
[ '--
|
45
|
-
[ '--log'
|
46
|
-
[ '--verbose'
|
30
|
+
[ '--help' , '-h' , GetoptLong::NO_ARGUMENT ] ,
|
31
|
+
[ '--init' , '-i' , GetoptLong::NO_ARGUMENT ] ,
|
32
|
+
[ '--parallel' , '-p' , GetoptLong::REQUIRED_ARGUMENT ] ,
|
33
|
+
[ '--env' , '-e' , GetoptLong::REQUIRED_ARGUMENT ] ,
|
34
|
+
[ '--url' , '-u' , GetoptLong::REQUIRED_ARGUMENT ] ,
|
35
|
+
[ '--server' , '-s' , GetoptLong::REQUIRED_ARGUMENT ] ,
|
36
|
+
[ '--log' , '-l' , GetoptLong::REQUIRED_ARGUMENT ] ,
|
37
|
+
[ '--verbose' , '-v' , GetoptLong::NO_ARGUMENT ] ,
|
47
38
|
)
|
48
39
|
end
|
49
40
|
|
41
|
+
def run!
|
42
|
+
@args = parse_args!
|
43
|
+
@opts = parse_opts!
|
44
|
+
|
45
|
+
case
|
46
|
+
when @args[0] == 'help' || @opts[:help]
|
47
|
+
usage!
|
48
|
+
|
49
|
+
when @args[0] == 'init' || @opts[:init]
|
50
|
+
init!
|
51
|
+
|
52
|
+
else
|
53
|
+
if @args.empty?
|
54
|
+
build!
|
55
|
+
else
|
56
|
+
usage!
|
57
|
+
exit(42)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
50
62
|
def build!
|
51
63
|
prepare!
|
52
64
|
|
53
|
-
|
65
|
+
load_config!
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
unless url
|
68
|
+
clear_cache!
|
69
|
+
start_server!
|
70
|
+
end
|
58
71
|
|
59
|
-
extract_urls!
|
72
|
+
extract_urls!
|
60
73
|
|
61
74
|
precompile_assets!
|
62
75
|
|
@@ -68,8 +81,8 @@ module RailsBuild
|
|
68
81
|
end
|
69
82
|
|
70
83
|
#
|
71
|
-
def CLI.
|
72
|
-
new(*args, &block).
|
84
|
+
def CLI.run!(*args, &block)
|
85
|
+
new(*args, &block).run!
|
73
86
|
end
|
74
87
|
|
75
88
|
#
|
@@ -82,86 +95,29 @@ module RailsBuild
|
|
82
95
|
attr_accessor :env
|
83
96
|
attr_accessor :parallel
|
84
97
|
|
85
|
-
#
|
86
|
-
def initialize(*args, &block)
|
87
|
-
setup!
|
88
|
-
|
89
|
-
@args = parse_args!
|
90
|
-
|
91
|
-
@opts = parse_opts!
|
92
|
-
|
93
|
-
if @opts[:help]
|
94
|
-
usage!
|
95
|
-
exit(42)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
#
|
100
|
-
def setup!
|
101
|
-
#
|
102
|
-
STDOUT.sync = true
|
103
|
-
STDERR.sync = true
|
104
|
-
|
105
|
-
#
|
106
|
-
ENV['SPRING_DISABLE'] = 'true'
|
107
|
-
ENV['DISABLE_SPRING'] = 'true'
|
108
|
-
|
109
|
-
#
|
110
|
-
%w[
|
111
|
-
fileutils pathname thread socket timeout time uri etc open-uri securerandom logger getoptlong rubygems json
|
112
|
-
].each do |stdlib|
|
113
|
-
require(stdlib)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
98
|
#
|
118
99
|
def prepare!
|
119
100
|
#
|
120
|
-
@rails_root = find_rails_root!
|
101
|
+
@rails_root = find_rails_root!
|
121
102
|
|
122
103
|
#
|
123
104
|
Dir.chdir(@rails_root)
|
124
105
|
|
125
106
|
#
|
126
|
-
|
127
|
-
require 'bundler/setup'
|
128
|
-
Bundler.setup(:require => false)
|
129
|
-
end
|
130
|
-
|
131
|
-
#
|
132
|
-
%w[
|
133
|
-
threadify persistent_http
|
134
|
-
].each do |gem|
|
135
|
-
begin
|
136
|
-
require(gem)
|
137
|
-
rescue LoadError
|
138
|
-
abort "add gem '#{ gem }' to your Gemfile"
|
139
|
-
end
|
140
|
-
end
|
107
|
+
@logger = Logger.new(@opts[:log] || STDERR)
|
141
108
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
nil
|
147
|
-
end
|
109
|
+
@env = @opts[:env] || ENV['RAILS_BUILD_ENV'] || ENV['RAILS_ENV']
|
110
|
+
@url = @opts[:url] || ENV['RAILS_BUILD_URL']
|
111
|
+
@parallel = @opts[:parallel] || ENV['RAILS_BUILD_PARALLEL']
|
112
|
+
@verbose = @opts[:verbose] || ENV['RAILS_BUILD_VERBOSE']
|
148
113
|
|
149
|
-
|
150
|
-
@
|
114
|
+
@uuid = ENV['RAILS_BUILD_UUID']
|
115
|
+
@time = ENV['RAILS_BUILD_TIME']
|
151
116
|
|
152
|
-
@uuid = ENV['RAILS_BUILD']
|
153
|
-
@time = ENV['RAILS_BUILD_TIME']
|
154
|
-
@url = @opts[:url] || ENV['RAILS_BUILD_URL']
|
155
|
-
@server = @opts[:server] || ENV['RAILS_BUILD_SERVER']
|
156
|
-
@env = @opts[:env] || ENV['RAILS_BUILD_ENV'] || ENV['RAILS_ENV']
|
157
|
-
@parallel = @opts[:parallel] || ENV['RAILS_BUILD_PARALLEL']
|
158
|
-
@verbose = @opts[:verbose] || ENV['RAILS_BUILD_VERBOSE']
|
159
|
-
|
160
|
-
@uuid ||= SecureRandom.uuid
|
161
|
-
@time ||= Time.now.utc
|
162
|
-
@server ||= 'passenger'
|
163
117
|
@env ||= 'production'
|
164
|
-
@parallel ||= (Etc.nprocessors
|
118
|
+
@parallel ||= (Etc.nprocessors - 1)
|
119
|
+
@uuid ||= SecureRandom.uuid
|
120
|
+
@time ||= Time.now.utc
|
165
121
|
|
166
122
|
unless @time.is_a?(Time)
|
167
123
|
@time = Time.parse(@time.to_s).utc
|
@@ -186,15 +142,13 @@ module RailsBuild
|
|
186
142
|
|
187
143
|
@started_at = Time.now
|
188
144
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
@server = server
|
193
|
-
end
|
145
|
+
mkdir!
|
146
|
+
|
147
|
+
@server = Server.new(cli: self)
|
194
148
|
end
|
195
149
|
|
196
150
|
#
|
197
|
-
def find_rails_root!(path)
|
151
|
+
def find_rails_root!(path = '.')
|
198
152
|
rails_root = File.expand_path(path.to_s)
|
199
153
|
|
200
154
|
loop do
|
@@ -238,11 +192,25 @@ module RailsBuild
|
|
238
192
|
STDERR.puts(usage)
|
239
193
|
end
|
240
194
|
|
195
|
+
#
|
196
|
+
def init!
|
197
|
+
config = SAMPLE_CONFIG
|
198
|
+
|
199
|
+
path = './config/rails_build.rb'
|
200
|
+
|
201
|
+
FileUtils.mkdir_p(File.dirname(path))
|
202
|
+
|
203
|
+
IO.binwrite(path, config)
|
204
|
+
|
205
|
+
STDERR.puts("please review #{ path } before running `rails_build`")
|
206
|
+
end
|
207
|
+
|
241
208
|
#
|
242
209
|
def mkdir!
|
243
210
|
FileUtils.rm_rf(@directory)
|
244
211
|
FileUtils.mkdir_p(@directory)
|
245
|
-
|
212
|
+
|
213
|
+
log(:info, "build: #{ @directory }")
|
246
214
|
end
|
247
215
|
|
248
216
|
#
|
@@ -256,48 +224,31 @@ module RailsBuild
|
|
256
224
|
ports =
|
257
225
|
(2000 .. 9000).to_a
|
258
226
|
|
259
|
-
start_server, stop_server = nil
|
260
|
-
|
261
|
-
@server.setup!
|
262
|
-
|
263
227
|
ports.each do |port|
|
264
228
|
next unless port_open?(port)
|
265
229
|
|
266
|
-
|
267
|
-
stop_server = @server.stop_command_for(port)
|
268
|
-
|
269
|
-
`#{ stop_server } 2>&1`.strip
|
270
|
-
|
271
|
-
log(:info, "cmd: #{ start_server }")
|
272
|
-
server_output = `#{ start_server } 2>&1`.strip
|
273
|
-
|
274
|
-
log(:info, "status: #{ $?.exitstatus }")
|
230
|
+
@server.start!(port:)
|
275
231
|
|
232
|
+
timeout = 11
|
276
233
|
t = Time.now.to_f
|
277
|
-
timeout = 10
|
278
234
|
i = 0
|
235
|
+
|
236
|
+
@proto = @config.fetch('force_ssl') ? 'https' : 'http'
|
279
237
|
url = nil
|
280
238
|
|
281
239
|
loop do
|
282
240
|
i += 1
|
241
|
+
sleep(rand(0.42))
|
283
242
|
|
284
243
|
begin
|
285
|
-
|
286
|
-
|
244
|
+
raise if port_open?(port)
|
245
|
+
url = "#{ @proto }://0.0.0.0:#{ port }"
|
287
246
|
@url = url
|
288
247
|
@port = port
|
289
248
|
break
|
290
249
|
rescue Object => e
|
291
|
-
if i > 2
|
292
|
-
log :error, "url: #{ url } ->"
|
293
|
-
log :error, "#{ e.message }(#{ e.class })\n"
|
294
|
-
log :error, "#{ server_output }\n\n"
|
295
|
-
end
|
296
|
-
|
297
250
|
if((Time.now.to_f - t) > timeout)
|
298
|
-
abort("could not start server inside of #{ timeout } seconds
|
299
|
-
else
|
300
|
-
sleep(rand(0.42))
|
251
|
+
abort("could not start server inside of #{ timeout } seconds")
|
301
252
|
end
|
302
253
|
end
|
303
254
|
end
|
@@ -310,76 +261,50 @@ module RailsBuild
|
|
310
261
|
unless @url
|
311
262
|
abort("could not start server on any of ports #{ ports.first } .. #{ ports.last }")
|
312
263
|
else
|
313
|
-
log(:info, "
|
264
|
+
log(:info, "url: #{ @url }")
|
314
265
|
end
|
315
266
|
|
316
|
-
# set assassins to ensure the server daemon never outlives the build script
|
317
|
-
# no matter how it is killed (even -9)
|
318
|
-
#
|
319
|
-
at_exit{
|
320
|
-
log(:info, "cmd: #{ stop_server }")
|
321
|
-
`#{ stop_server } >/dev/null 2>&1`
|
322
|
-
log(:info, "status: #{ $?.exitstatus }")
|
323
|
-
log(:info, "stopped server #{ @url }")
|
324
|
-
@server.cleanup!(:port => @port)
|
325
|
-
}
|
326
|
-
|
327
|
-
assassin = <<-__
|
328
|
-
pid = #{ Process.pid }
|
329
|
-
|
330
|
-
4242.times do
|
331
|
-
begin
|
332
|
-
Process.kill(0, pid)
|
333
|
-
rescue Object => e
|
334
|
-
if e.is_a?(Errno::ESRCH)
|
335
|
-
`#{ stop_server } >/dev/null 2>&1`
|
336
|
-
Process.kill(-15, pid) rescue nil
|
337
|
-
sleep(rand + rand)
|
338
|
-
Process.kill(-9, pid) rescue nil
|
339
|
-
end
|
340
|
-
exit
|
341
|
-
end
|
342
|
-
sleep(1 + rand)
|
343
|
-
end
|
344
|
-
__
|
345
|
-
IO.binwrite('tmp/build-assassin.rb', assassin)
|
346
|
-
cmd = "nohup ruby tmp/build-assassin.rb >/dev/null 2>&1 &"
|
347
|
-
system cmd
|
348
|
-
|
349
267
|
#
|
350
268
|
@started_at = Time.now
|
351
269
|
@url
|
352
270
|
end
|
353
271
|
|
354
272
|
#
|
355
|
-
def
|
356
|
-
|
273
|
+
def load_config!
|
274
|
+
unless test(?s, RailsBuild.config_path)
|
275
|
+
log(:error, "no config found in #{ RailsBuild.config_path }")
|
276
|
+
abort
|
277
|
+
end
|
357
278
|
|
358
|
-
|
279
|
+
Tempfile.open do |tmp|
|
280
|
+
env = {RAILS_ENV:@env, RAILS_BUILD_CONFIG_DUMP:tmp.path}
|
281
|
+
spawn('rails', 'runner', 'RailsBuild.dump_config!', env:)
|
282
|
+
json = IO.binread(tmp.path)
|
283
|
+
hash = JSON.parse(json)
|
359
284
|
|
360
|
-
|
361
|
-
raise "failed to get build urls from #{ build_url }"
|
285
|
+
@config = Configuration.new(hash)
|
362
286
|
end
|
287
|
+
end
|
363
288
|
|
364
|
-
|
289
|
+
def extract_urls!
|
290
|
+
path = @config.path
|
291
|
+
urls = @config.urls
|
365
292
|
|
366
|
-
|
367
|
-
|
293
|
+
if urls.size == 0
|
294
|
+
abort("failed to find any rails_build urls in:\n#{ @config.to_json }")
|
368
295
|
end
|
369
296
|
|
370
|
-
urls = @_build['urls']
|
371
|
-
|
372
297
|
urls.map!{|url| url_for(url)}
|
373
298
|
|
374
|
-
log(:info, "extracted #{ urls.size } url(s) to build from #{
|
299
|
+
log(:info, "extracted #{ urls.size } url(s) to build from #{ path }")
|
375
300
|
|
376
301
|
@urls = urls
|
377
302
|
end
|
378
303
|
|
379
304
|
#
|
380
305
|
def clear_cache!
|
381
|
-
|
382
|
-
spawn "rails runner 'Rails.cache.clear'"
|
306
|
+
spawn "rails tmp:cache:clear", error: false
|
307
|
+
spawn "rails runner 'Rails.cache.clear'", error: false
|
383
308
|
end
|
384
309
|
|
385
310
|
#
|
@@ -389,10 +314,10 @@ module RailsBuild
|
|
389
314
|
|
390
315
|
if test(?d, @asset_dir)
|
391
316
|
@asset_tmp = File.join(@rails_root, "tmp/assets-build-#{ @uuid }")
|
392
|
-
FileUtils.mv(@asset_dir, @asset_tmp)
|
317
|
+
FileUtils.mv(@asset_dir, @asset_tmp)
|
393
318
|
end
|
394
319
|
|
395
|
-
spawn "RAILS_ENV=production rake assets:precompile"
|
320
|
+
spawn "RAILS_ENV=production DISABLE_SPRING=true rake assets:precompile"
|
396
321
|
|
397
322
|
assets = Dir.glob(File.join(@rails_root, 'public/assets/**/**'))
|
398
323
|
|
@@ -403,10 +328,29 @@ module RailsBuild
|
|
403
328
|
|
404
329
|
#
|
405
330
|
def rsync_public!
|
406
|
-
|
331
|
+
commands = [
|
332
|
+
"rsync -avz ./public/ #{ @directory }",
|
333
|
+
"cp -ru ./public/ #{ @directory }",
|
334
|
+
proc{ FileUtils.cp_r('./public', @directory) }
|
335
|
+
]
|
336
|
+
|
337
|
+
rsynced = false
|
338
|
+
|
339
|
+
commands.each do |command|
|
340
|
+
begin
|
341
|
+
spawn(command)
|
342
|
+
rsynced = true
|
343
|
+
break
|
344
|
+
rescue
|
345
|
+
next
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
unless rsynced
|
350
|
+
abort "failed to rsync ./public to `#{ @directory }`"
|
351
|
+
end
|
407
352
|
|
408
353
|
count = 0
|
409
|
-
|
410
354
|
Dir.glob(File.join(@directory, '**/**')).each{ count += 1 }
|
411
355
|
|
412
356
|
log(:info, "rsync'd #{ count } files")
|
@@ -417,93 +361,70 @@ module RailsBuild
|
|
417
361
|
end
|
418
362
|
end
|
419
363
|
|
420
|
-
|
421
|
-
def parallel_build!
|
422
|
-
|
364
|
+
#
|
365
|
+
def parallel_build!
|
366
|
+
size = @parallel
|
423
367
|
|
424
368
|
stats = {
|
425
|
-
:success => [],
|
369
|
+
:success => [],
|
370
|
+
:missing => [],
|
371
|
+
:failure => [],
|
426
372
|
}
|
427
373
|
|
428
|
-
times =
|
429
|
-
|
430
|
-
avg = nil
|
431
|
-
|
432
|
-
Thread.new do
|
433
|
-
Thread.current.abort_on_exception = true
|
434
|
-
total = 0.0
|
435
|
-
n = 0
|
436
|
-
loop do
|
437
|
-
while(time = times.pop)
|
438
|
-
total += time
|
439
|
-
n += 1
|
440
|
-
avg = (total / n).round(2)
|
441
|
-
end
|
442
|
-
end
|
443
|
-
end
|
444
|
-
|
445
|
-
a = Time.now.to_f
|
446
|
-
|
447
|
-
_stats =
|
448
|
-
@urls.threadify(n) do |url|
|
449
|
-
uri = uri_for(url)
|
450
|
-
path = path_for(uri)
|
451
|
-
upath = uri.path
|
452
|
-
rpath = relative_path(path, :from => @directory)
|
374
|
+
times = []
|
453
375
|
|
454
|
-
|
376
|
+
Parallel.each(@urls, in_threads: 8) do |url|
|
377
|
+
uri = uri_for(url)
|
378
|
+
path = path_for(uri)
|
455
379
|
|
456
|
-
|
380
|
+
rpath = relative_path(path, :from => @directory)
|
457
381
|
|
458
|
-
|
459
|
-
|
382
|
+
code = nil
|
383
|
+
body = nil
|
460
384
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
case code
|
466
|
-
when 200
|
467
|
-
write_path(path, body)
|
468
|
-
log(:info, "#{ label }: #{ upath } -> #{ rpath } (t:#{ _e }s)")
|
469
|
-
[:success, url]
|
470
|
-
when 404
|
471
|
-
log(:error, "#{ label }: #{ upath }")
|
472
|
-
[:missing, url]
|
473
|
-
when 500
|
474
|
-
log(:error, "#{ label }: #{ upath }")
|
475
|
-
[:failure, url]
|
476
|
-
else
|
477
|
-
log(:error, "#{ label }: #{ upath }")
|
478
|
-
[:failure, url]
|
385
|
+
time =
|
386
|
+
timing do
|
387
|
+
code, body = http_get(uri)
|
388
|
+
write_path(path, body) if code == 200
|
479
389
|
end
|
480
|
-
end
|
481
|
-
|
482
|
-
_stats.each{|stat, url| stats[stat].push(url)}
|
483
390
|
|
484
|
-
|
391
|
+
times.push(time)
|
392
|
+
ts = times.dup
|
393
|
+
rps = (ts.size / ts.sum).round(4)
|
394
|
+
msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
|
395
|
+
|
396
|
+
case code
|
397
|
+
when 200
|
398
|
+
log(:info, msg)
|
399
|
+
stats[:success].push(url)
|
400
|
+
when 404
|
401
|
+
log(:error, msg)
|
402
|
+
stats[:missing].push(url)
|
403
|
+
else
|
404
|
+
log(:error, msg)
|
405
|
+
stats[:failure].push(url)
|
406
|
+
end
|
407
|
+
end
|
485
408
|
|
486
|
-
borked =
|
409
|
+
borked = 0
|
487
410
|
|
488
411
|
if stats[:missing].size > 0
|
489
|
-
borked
|
490
|
-
log(:error, "missing on #{ stats[:missing].size }
|
412
|
+
borked += stats[:missing].size
|
413
|
+
log(:error, "missing on #{ stats[:missing].size } url(s)")
|
491
414
|
end
|
492
415
|
|
493
416
|
if stats[:failure].size > 0
|
494
|
-
borked
|
495
|
-
log(:error, "failure on #{ stats[:failure].size }
|
417
|
+
borked += stats[:failure].size
|
418
|
+
log(:error, "failure on #{ stats[:failure].size } url(s)")
|
496
419
|
end
|
497
420
|
|
498
|
-
if borked
|
499
|
-
exit(
|
421
|
+
if borked > 0
|
422
|
+
exit(borked)
|
500
423
|
end
|
501
424
|
|
502
|
-
|
503
|
-
n = @urls.size
|
504
|
-
rps = (n / elapsed).round(2)
|
425
|
+
rps = (times.size / times.sum).round(4)
|
505
426
|
|
506
|
-
log(:info, "downloaded #{
|
427
|
+
log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
|
507
428
|
|
508
429
|
@urls
|
509
430
|
end
|
@@ -511,39 +432,50 @@ module RailsBuild
|
|
511
432
|
#
|
512
433
|
def finalize!
|
513
434
|
@finished_at = Time.now
|
435
|
+
|
514
436
|
elapsed = (@finished_at.to_f - @started_at.to_f)
|
437
|
+
|
515
438
|
log(:info, "build time - #{ hms(elapsed) }")
|
439
|
+
|
440
|
+
# because netlify refuses to deploy from a symlink!
|
516
441
|
on_netlify = ENV['DEPLOY_PRIME_URL'].to_s =~ /netlify/
|
517
|
-
|
442
|
+
|
443
|
+
cp = on_netlify ? 'cp_r' : 'ln_s'
|
444
|
+
|
518
445
|
build = File.join(@rails_root, 'build')
|
446
|
+
|
519
447
|
FileUtils.rm_rf(build)
|
520
|
-
FileUtils.send(
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
:warn_timeout => 1,
|
532
|
-
:force_retry => true,
|
533
|
-
})
|
534
|
-
)
|
448
|
+
FileUtils.send(cp, @directory, build)
|
449
|
+
|
450
|
+
#log(:info, "to preview run: ruby -run -ehttpd ./build/ -p4242")
|
451
|
+
end
|
452
|
+
|
453
|
+
def timing(&block)
|
454
|
+
t = Time.now.to_f
|
455
|
+
|
456
|
+
block.call
|
457
|
+
|
458
|
+
(Time.now.to_f - t).round(2)
|
535
459
|
end
|
536
460
|
|
537
461
|
def http_get(url)
|
538
|
-
|
539
|
-
|
462
|
+
uri = URI.parse(url.to_s)
|
463
|
+
|
464
|
+
response =
|
465
|
+
begin
|
466
|
+
Net::HTTP.get_response(uri)
|
467
|
+
rescue
|
468
|
+
[code = 500, body = '']
|
469
|
+
end
|
540
470
|
|
541
471
|
if response.is_a?(Net::HTTPRedirection)
|
542
472
|
location = response['Location']
|
473
|
+
|
543
474
|
if location.to_s == url.to_s
|
544
475
|
log(:fatal, "circular redirection on #{ url }")
|
545
476
|
exit(1)
|
546
477
|
end
|
478
|
+
|
547
479
|
return http_get(location)
|
548
480
|
end
|
549
481
|
|
@@ -566,18 +498,33 @@ module RailsBuild
|
|
566
498
|
#
|
567
499
|
def path_for(url)
|
568
500
|
uri = uri_for(url)
|
501
|
+
path = nil
|
569
502
|
|
570
503
|
case
|
571
504
|
when uri.path=='/' || uri.path=='.'
|
572
505
|
path = File.join(@directory, 'index.html')
|
506
|
+
|
573
507
|
else
|
574
508
|
path = File.join(@directory, uri.path)
|
509
|
+
|
575
510
|
dirname, basename = File.split(path)
|
576
511
|
base, ext = basename.split('.', 2)
|
577
|
-
|
578
|
-
|
512
|
+
|
513
|
+
case
|
514
|
+
when uri.path.end_with?('/')
|
515
|
+
path =
|
516
|
+
File.join(path, 'index.html')
|
517
|
+
|
518
|
+
when ext.nil?
|
519
|
+
path =
|
520
|
+
if @config.fetch('index_html')
|
521
|
+
File.join(path, 'index.html')
|
522
|
+
else
|
523
|
+
path + '.html'
|
524
|
+
end
|
579
525
|
end
|
580
526
|
end
|
527
|
+
|
581
528
|
path
|
582
529
|
end
|
583
530
|
|
@@ -606,13 +553,14 @@ module RailsBuild
|
|
606
553
|
uri = URI.parse(url.to_s)
|
607
554
|
|
608
555
|
if uri.absolute?
|
556
|
+
uri.path = path
|
609
557
|
uri.to_s
|
610
558
|
else
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
559
|
+
rel = @url ? URI.parse(@url) : URI.parse('')
|
560
|
+
rel.path = absolute_path_for(uri.path)
|
561
|
+
rel.query = uri.query
|
562
|
+
rel.fragment = uri.fragment
|
563
|
+
rel.to_s
|
616
564
|
end
|
617
565
|
end
|
618
566
|
|
@@ -636,14 +584,6 @@ module RailsBuild
|
|
636
584
|
[hours.to_i, minutes.to_s, seconds]
|
637
585
|
end
|
638
586
|
|
639
|
-
#
|
640
|
-
def stopwatch(&block)
|
641
|
-
a = Time.now
|
642
|
-
result = block.call
|
643
|
-
b = Time.now
|
644
|
-
[result, b.to_f - a.to_f]
|
645
|
-
end
|
646
|
-
|
647
587
|
#
|
648
588
|
def port_open?(port, options = {})
|
649
589
|
seconds = options[:timeout] || 1
|
@@ -675,7 +615,8 @@ module RailsBuild
|
|
675
615
|
|
676
616
|
#
|
677
617
|
def absolute_path_for(*args)
|
678
|
-
|
618
|
+
trailing_slash = args.join.end_with?('/') ? '/' : ''
|
619
|
+
path = ('/' + paths_for(*args).join('/') + trailing_slash).squeeze('/')
|
679
620
|
path unless path.strip.empty?
|
680
621
|
end
|
681
622
|
|
@@ -698,121 +639,202 @@ module RailsBuild
|
|
698
639
|
end
|
699
640
|
|
700
641
|
#
|
701
|
-
def spawn(
|
702
|
-
|
642
|
+
def spawn(arg, *args, **kws)
|
643
|
+
command = [arg, *args]
|
644
|
+
|
645
|
+
env = kws.fetch(:env){ {} }
|
646
|
+
error = kws.fetch(:error){ true }
|
647
|
+
quiet = kws.fetch(:quiet){ false }
|
648
|
+
stdin = kws.fetch(:stdin){ '' }
|
649
|
+
|
650
|
+
env.transform_keys!(&:to_s)
|
651
|
+
env.transform_values!(&:to_s)
|
652
|
+
|
653
|
+
pid = nil
|
654
|
+
status = nil
|
655
|
+
stdout = nil
|
656
|
+
stderr = nil
|
657
|
+
|
658
|
+
Tempfile.open do |i|
|
659
|
+
i.write(stdin)
|
660
|
+
i.flush
|
661
|
+
|
662
|
+
Tempfile.open do |o|
|
663
|
+
Tempfile.open do |e|
|
664
|
+
redirects = {:in => i.path, :out => o.path, :err => e.path}
|
665
|
+
|
666
|
+
pid = Process.spawn(env, *command, redirects)
|
667
|
+
|
668
|
+
Process.wait(pid)
|
669
|
+
|
670
|
+
status = $?.exitstatus
|
703
671
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
672
|
+
stdout = IO.binread(o.path)
|
673
|
+
stderr = IO.binread(e.path)
|
674
|
+
end
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
unless status == 0
|
679
|
+
unless kws[:quiet] == true
|
680
|
+
log(:error, "#{ command.join(' ') } ###===>>> #{ status }\nSTDOUT:\n#{ stdout }\n\STDERR:\n#{ stderr }")
|
681
|
+
exit(status)
|
682
|
+
end
|
708
683
|
end
|
709
684
|
|
710
|
-
|
685
|
+
{command:, pid:, env:, status:, stdin:, stdout:, stderr:}
|
711
686
|
end
|
712
687
|
end
|
713
688
|
|
714
689
|
#
|
715
690
|
class Server
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
case type
|
720
|
-
when /puma/
|
721
|
-
Puma.new(cli, *args, &block)
|
722
|
-
when /passenger/
|
723
|
-
Passenger.new(cli, *args, &block)
|
724
|
-
else
|
725
|
-
nil
|
726
|
-
end
|
727
|
-
end
|
728
|
-
|
729
|
-
def initialize(cli)
|
691
|
+
attr_reader :pid
|
692
|
+
|
693
|
+
def initialize(cli:)
|
730
694
|
@cli = cli
|
731
|
-
end
|
732
695
|
|
733
|
-
|
734
|
-
|
696
|
+
@env = @cli.env
|
697
|
+
@directory = @cli.directory
|
698
|
+
@rails_root = @cli.rails_root
|
699
|
+
@parallel = @cli.parallel
|
700
|
+
@uuid = @cli.uuid
|
701
|
+
|
702
|
+
@thread = nil
|
703
|
+
@pid = nil
|
735
704
|
end
|
736
705
|
|
737
|
-
|
738
|
-
|
739
|
-
|
706
|
+
def start!(port:)
|
707
|
+
system("#{ version_command } >/dev/null 2>&1") ||
|
708
|
+
abort("app fails to load via: #{ version_command }")
|
740
709
|
|
741
|
-
|
742
|
-
@directory = @cli.directory
|
743
|
-
@rails_root = @cli.rails_root
|
744
|
-
@parallel = @cli.parallel
|
710
|
+
q = Queue.new
|
745
711
|
|
746
|
-
|
747
|
-
@pumactl = "bundle exec pumactl"
|
712
|
+
cmd = start_command_for(port)
|
748
713
|
|
749
|
-
|
750
|
-
@statefile = @cli.relative_path(File.join(@directory, ".puma-state.txt"), :from => @cli.rails_root)
|
751
|
-
end
|
714
|
+
log = './tmp/rails_build_server.log'
|
752
715
|
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
--environment=#{ @env }
|
760
|
-
--workers=#{ @parallel }
|
761
|
-
--config=/dev/null
|
762
|
-
--threads=1:1
|
763
|
-
--preload
|
764
|
-
--daemon
|
765
|
-
./config.ru
|
766
|
-
].join(' ').strip
|
716
|
+
@cli.log(:info, "server: #{ cmd } > #{ log } 2>&1")
|
717
|
+
|
718
|
+
@thread = Thread.new do
|
719
|
+
Thread.current.abort_on_exception = true
|
720
|
+
pipe = IO.popen("#{ cmd } > #{ log } 2>&1")
|
721
|
+
q.push(pipe.pid)
|
767
722
|
end
|
768
723
|
|
769
|
-
|
724
|
+
@pid = q.pop
|
725
|
+
|
726
|
+
@cli.log(:info, "pid: #{ @pid }")
|
727
|
+
|
728
|
+
@assassin = Assassin.ate(@pid)
|
729
|
+
|
730
|
+
at_exit{ stop! }
|
731
|
+
end
|
732
|
+
|
733
|
+
def version_command
|
734
|
+
cmd_for(
|
770
735
|
%W[
|
771
|
-
|
772
|
-
|
773
|
-
--state=#{ @statefile }
|
774
|
-
stop
|
775
|
-
].join(' ').strip
|
776
|
-
end
|
736
|
+
RAILS_ENV=#{ @env }
|
737
|
+
DISABLE_SPRING=true
|
777
738
|
|
778
|
-
|
779
|
-
|
780
|
-
|
739
|
+
rails --version
|
740
|
+
]
|
741
|
+
)
|
781
742
|
end
|
782
743
|
|
783
|
-
|
784
|
-
|
785
|
-
|
744
|
+
def start_command_for(port)
|
745
|
+
cmd_for(
|
746
|
+
%W[
|
747
|
+
RAILS_ENV=#{ @env }
|
748
|
+
DISABLE_SPRING=true
|
786
749
|
|
787
|
-
|
788
|
-
@directory = @cli.directory
|
789
|
-
@rails_root = @cli.rails_root
|
790
|
-
@parallel = @cli.parallel
|
750
|
+
RAILS_BUILD=#{ @uuid }
|
791
751
|
|
792
|
-
|
793
|
-
|
752
|
+
RAILS_SERVE_STATIC_FILES=true
|
753
|
+
RAILS_LOG_TO_STDOUT=true
|
754
|
+
WEB_CONCURRENCY=#{ @parallel.to_s }
|
794
755
|
|
795
|
-
|
796
|
-
"#{ @passenger } start --daemonize --environment #{ @env } --port #{ port } --max-pool-size #{ @parallel }"
|
797
|
-
end
|
756
|
+
rails server
|
798
757
|
|
799
|
-
|
800
|
-
|
801
|
-
|
758
|
+
--environment=#{ @env }
|
759
|
+
--port=#{ port }
|
760
|
+
--binding=0.0.0.0
|
761
|
+
--log-to-stdout
|
762
|
+
]
|
763
|
+
)
|
764
|
+
end
|
802
765
|
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
766
|
+
def cmd_for(arg, *args)
|
767
|
+
[arg, *args].flatten.compact.join(' ').squeeze(' ').strip
|
768
|
+
end
|
769
|
+
|
770
|
+
def stop!
|
771
|
+
kill!(@pid)
|
772
|
+
@thread.kill
|
773
|
+
@cli.log(:info, "stopped: #{ @pid }")
|
774
|
+
end
|
808
775
|
|
809
|
-
|
776
|
+
def kill!(pid)
|
777
|
+
42.times do
|
810
778
|
begin
|
811
|
-
|
812
|
-
|
813
|
-
|
779
|
+
Process.kill(0, pid)
|
780
|
+
return(true)
|
781
|
+
rescue Object => e
|
782
|
+
if e.is_a?(Errno::ESRCH)
|
783
|
+
Process.kill(-15, pid) rescue nil
|
784
|
+
sleep(rand + rand)
|
785
|
+
Process.kill(-9, pid) rescue nil
|
786
|
+
end
|
814
787
|
end
|
788
|
+
sleep(0.42 + rand)
|
815
789
|
end
|
790
|
+
return(false)
|
816
791
|
end
|
817
792
|
end
|
818
793
|
end
|
794
|
+
|
795
|
+
END {
|
796
|
+
require_relative '../lib/rails_build.rb'
|
797
|
+
|
798
|
+
STDOUT.sync = true
|
799
|
+
STDERR.sync = true
|
800
|
+
|
801
|
+
RailsBuild::CLI.run!
|
802
|
+
}
|
803
|
+
|
804
|
+
module RailsBuild
|
805
|
+
SAMPLE_CONFIG = <<~'__'
|
806
|
+
<<~________
|
807
|
+
|
808
|
+
this file should to enumerate all the urls you'd like to build
|
809
|
+
|
810
|
+
the contents of your ./public directory, and any assets, are automaticaly included
|
811
|
+
|
812
|
+
therefore you need only declare which dynamic urls, that is to say, 'routes'
|
813
|
+
|
814
|
+
you would like included in your build
|
815
|
+
|
816
|
+
it is not loaded except during build time, and will not affect your normal rails app in any way
|
817
|
+
|
818
|
+
________
|
819
|
+
|
820
|
+
|
821
|
+
RailsBuild.configure do |config|
|
822
|
+
|
823
|
+
# most of the time you are going to want your route included, which will
|
824
|
+
# translate into an ./index.html being output in the build
|
825
|
+
#
|
826
|
+
|
827
|
+
config.urls << '/'
|
828
|
+
|
829
|
+
# include any/all additional routes youd' like built thusly
|
830
|
+
#
|
831
|
+
|
832
|
+
Post.each do |post|
|
833
|
+
config.urls << "/posts/#{ post.id }"
|
834
|
+
end
|
835
|
+
|
836
|
+
# thats it! - now just run `rails_build` and you are GTG
|
837
|
+
|
838
|
+
end
|
839
|
+
__
|
840
|
+
end
|