rails_build 1.2.0 → 2.4.2
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 +70 -127
- data/Rakefile +437 -22
- data/bin/rails_build +456 -355
- data/config/rails_build.rb +33 -0
- data/lib/rails_build/_lib.rb +87 -0
- data/lib/rails_build.rb +188 -17
- data/rails_build.gemspec +42 -0
- metadata +32 -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
|
-
|
54
|
-
|
55
|
-
start_server! unless url
|
65
|
+
load_config!
|
56
66
|
|
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 = DATA.read.strip
|
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")
|
@@ -418,92 +362,151 @@ module RailsBuild
|
|
418
362
|
end
|
419
363
|
|
420
364
|
#
|
421
|
-
|
422
|
-
|
365
|
+
=begin
|
366
|
+
def parallel_build!
|
367
|
+
size = @parallel
|
423
368
|
|
424
369
|
stats = {
|
425
|
-
:success => [],
|
370
|
+
:success => [],
|
371
|
+
:missing => [],
|
372
|
+
:failure => [],
|
426
373
|
}
|
427
374
|
|
428
|
-
times =
|
375
|
+
times = []
|
376
|
+
|
377
|
+
elapsed = timing do
|
378
|
+
ThreadPool.new(size:) do |tp|
|
379
|
+
tp.run do
|
380
|
+
@urls.each{|url| tp.process!(url:)}
|
381
|
+
end
|
429
382
|
|
430
|
-
|
383
|
+
tp.process do |url:|
|
384
|
+
uri = uri_for(url)
|
385
|
+
path = path_for(uri)
|
431
386
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
387
|
+
upath = uri.path
|
388
|
+
rpath = relative_path(path, :from => @directory)
|
389
|
+
|
390
|
+
code = nil
|
391
|
+
body = nil
|
392
|
+
|
393
|
+
time =
|
394
|
+
timing do
|
395
|
+
code, body = http_get(uri)
|
396
|
+
write_path(path, body) if code == 200
|
397
|
+
end
|
398
|
+
|
399
|
+
tp.success!(url:, rpath:, time:, code:)
|
400
|
+
end
|
401
|
+
|
402
|
+
tp.success do |url:, rpath:, time:, code:|
|
403
|
+
times.push(time)
|
404
|
+
rps = (times.size / times.sum).round(4)
|
405
|
+
msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
|
406
|
+
|
407
|
+
case code
|
408
|
+
when 200
|
409
|
+
log(:info, msg)
|
410
|
+
stats[:success].push(url)
|
411
|
+
when 404
|
412
|
+
log(:error, msg)
|
413
|
+
stats[:missing].push(url)
|
414
|
+
else
|
415
|
+
log(:error, msg)
|
416
|
+
stats[:failure].push(url)
|
417
|
+
end
|
441
418
|
end
|
442
419
|
end
|
443
420
|
end
|
444
421
|
|
445
|
-
|
422
|
+
borked = 0
|
423
|
+
|
424
|
+
if stats[:missing].size > 0
|
425
|
+
borked += stats[:missing].size
|
426
|
+
log(:error, "missing on #{ stats[:missing].size } url(s)")
|
427
|
+
end
|
428
|
+
|
429
|
+
if stats[:failure].size > 0
|
430
|
+
borked += stats[:failure].size
|
431
|
+
log(:error, "failure on #{ stats[:failure].size } url(s)")
|
432
|
+
end
|
433
|
+
|
434
|
+
if borked > 0
|
435
|
+
exit(borked)
|
436
|
+
end
|
437
|
+
|
438
|
+
rps = (times.size / times.sum).round(4)
|
446
439
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
440
|
+
log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
|
441
|
+
|
442
|
+
@urls
|
443
|
+
end
|
444
|
+
=end
|
445
|
+
|
446
|
+
#
|
447
|
+
def parallel_build!
|
448
|
+
size = @parallel
|
453
449
|
|
454
|
-
|
450
|
+
stats = {
|
451
|
+
:success => [],
|
452
|
+
:missing => [],
|
453
|
+
:failure => [],
|
454
|
+
}
|
455
455
|
|
456
|
-
|
456
|
+
times = []
|
457
457
|
|
458
|
-
|
459
|
-
|
458
|
+
Parallel.each(@urls, in_threads: 8) do |url|
|
459
|
+
uri = uri_for(url)
|
460
|
+
path = path_for(uri)
|
460
461
|
|
461
|
-
|
462
|
+
rpath = relative_path(path, :from => @directory)
|
462
463
|
|
463
|
-
|
464
|
+
code = nil
|
465
|
+
body = nil
|
464
466
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
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]
|
467
|
+
time =
|
468
|
+
timing do
|
469
|
+
code, body = http_get(uri)
|
470
|
+
write_path(path, body) if code == 200
|
479
471
|
end
|
480
|
-
end
|
481
|
-
|
482
|
-
_stats.each{|stat, url| stats[stat].push(url)}
|
483
472
|
|
484
|
-
|
473
|
+
times.push(time)
|
474
|
+
ts = times.dup
|
475
|
+
rps = (ts.size / ts.sum).round(4)
|
476
|
+
msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
|
477
|
+
|
478
|
+
case code
|
479
|
+
when 200
|
480
|
+
log(:info, msg)
|
481
|
+
stats[:success].push(url)
|
482
|
+
when 404
|
483
|
+
log(:error, msg)
|
484
|
+
stats[:missing].push(url)
|
485
|
+
else
|
486
|
+
log(:error, msg)
|
487
|
+
stats[:failure].push(url)
|
488
|
+
end
|
489
|
+
end
|
485
490
|
|
486
|
-
borked =
|
491
|
+
borked = 0
|
487
492
|
|
488
493
|
if stats[:missing].size > 0
|
489
|
-
borked
|
490
|
-
log(:error, "missing on #{ stats[:missing].size }
|
494
|
+
borked += stats[:missing].size
|
495
|
+
log(:error, "missing on #{ stats[:missing].size } url(s)")
|
491
496
|
end
|
492
497
|
|
493
498
|
if stats[:failure].size > 0
|
494
|
-
borked
|
495
|
-
log(:error, "failure on #{ stats[:failure].size }
|
499
|
+
borked += stats[:failure].size
|
500
|
+
log(:error, "failure on #{ stats[:failure].size } url(s)")
|
496
501
|
end
|
497
502
|
|
498
|
-
if borked
|
499
|
-
exit(
|
503
|
+
if borked > 0
|
504
|
+
exit(borked)
|
500
505
|
end
|
501
506
|
|
502
|
-
|
503
|
-
n = @urls.size
|
504
|
-
rps = (n / elapsed).round(2)
|
507
|
+
rps = (times.size / times.sum).round(4)
|
505
508
|
|
506
|
-
log(:info, "downloaded #{
|
509
|
+
log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
|
507
510
|
|
508
511
|
@urls
|
509
512
|
end
|
@@ -511,39 +514,50 @@ module RailsBuild
|
|
511
514
|
#
|
512
515
|
def finalize!
|
513
516
|
@finished_at = Time.now
|
517
|
+
|
514
518
|
elapsed = (@finished_at.to_f - @started_at.to_f)
|
519
|
+
|
515
520
|
log(:info, "build time - #{ hms(elapsed) }")
|
521
|
+
|
522
|
+
# because netlify refuses to deploy from a symlink!
|
516
523
|
on_netlify = ENV['DEPLOY_PRIME_URL'].to_s =~ /netlify/
|
517
|
-
|
524
|
+
|
525
|
+
cp = on_netlify ? 'cp_r' : 'ln_s'
|
526
|
+
|
518
527
|
build = File.join(@rails_root, 'build')
|
528
|
+
|
519
529
|
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
|
-
)
|
530
|
+
FileUtils.send(cp, @directory, build)
|
531
|
+
|
532
|
+
#log(:info, "to preview run: ruby -run -ehttpd ./build/ -p4242")
|
533
|
+
end
|
534
|
+
|
535
|
+
def timing(&block)
|
536
|
+
t = Time.now.to_f
|
537
|
+
|
538
|
+
block.call
|
539
|
+
|
540
|
+
(Time.now.to_f - t).round(2)
|
535
541
|
end
|
536
542
|
|
537
543
|
def http_get(url)
|
538
|
-
|
539
|
-
|
544
|
+
uri = URI.parse(url.to_s)
|
545
|
+
|
546
|
+
response =
|
547
|
+
begin
|
548
|
+
Net::HTTP.get_response(uri)
|
549
|
+
rescue
|
550
|
+
[code = 500, body = '']
|
551
|
+
end
|
540
552
|
|
541
553
|
if response.is_a?(Net::HTTPRedirection)
|
542
554
|
location = response['Location']
|
555
|
+
|
543
556
|
if location.to_s == url.to_s
|
544
557
|
log(:fatal, "circular redirection on #{ url }")
|
545
558
|
exit(1)
|
546
559
|
end
|
560
|
+
|
547
561
|
return http_get(location)
|
548
562
|
end
|
549
563
|
|
@@ -566,18 +580,33 @@ module RailsBuild
|
|
566
580
|
#
|
567
581
|
def path_for(url)
|
568
582
|
uri = uri_for(url)
|
583
|
+
path = nil
|
569
584
|
|
570
585
|
case
|
571
586
|
when uri.path=='/' || uri.path=='.'
|
572
587
|
path = File.join(@directory, 'index.html')
|
588
|
+
|
573
589
|
else
|
574
590
|
path = File.join(@directory, uri.path)
|
591
|
+
|
575
592
|
dirname, basename = File.split(path)
|
576
593
|
base, ext = basename.split('.', 2)
|
577
|
-
|
578
|
-
|
594
|
+
|
595
|
+
case
|
596
|
+
when uri.path.end_with?('/')
|
597
|
+
path =
|
598
|
+
File.join(path, 'index.html')
|
599
|
+
|
600
|
+
when ext.nil?
|
601
|
+
path =
|
602
|
+
if @config.fetch('index_html')
|
603
|
+
File.join(path, 'index.html')
|
604
|
+
else
|
605
|
+
path + '.html'
|
606
|
+
end
|
579
607
|
end
|
580
608
|
end
|
609
|
+
|
581
610
|
path
|
582
611
|
end
|
583
612
|
|
@@ -606,13 +635,14 @@ module RailsBuild
|
|
606
635
|
uri = URI.parse(url.to_s)
|
607
636
|
|
608
637
|
if uri.absolute?
|
638
|
+
uri.path = path
|
609
639
|
uri.to_s
|
610
640
|
else
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
641
|
+
rel = @url ? URI.parse(@url) : URI.parse('')
|
642
|
+
rel.path = absolute_path_for(uri.path)
|
643
|
+
rel.query = uri.query
|
644
|
+
rel.fragment = uri.fragment
|
645
|
+
rel.to_s
|
616
646
|
end
|
617
647
|
end
|
618
648
|
|
@@ -636,14 +666,6 @@ module RailsBuild
|
|
636
666
|
[hours.to_i, minutes.to_s, seconds]
|
637
667
|
end
|
638
668
|
|
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
669
|
#
|
648
670
|
def port_open?(port, options = {})
|
649
671
|
seconds = options[:timeout] || 1
|
@@ -675,7 +697,8 @@ module RailsBuild
|
|
675
697
|
|
676
698
|
#
|
677
699
|
def absolute_path_for(*args)
|
678
|
-
|
700
|
+
trailing_slash = args.join.end_with?('/') ? '/' : ''
|
701
|
+
path = ('/' + paths_for(*args).join('/') + trailing_slash).squeeze('/')
|
679
702
|
path unless path.strip.empty?
|
680
703
|
end
|
681
704
|
|
@@ -698,121 +721,199 @@ module RailsBuild
|
|
698
721
|
end
|
699
722
|
|
700
723
|
#
|
701
|
-
def spawn(
|
702
|
-
|
724
|
+
def spawn(arg, *args, **kws)
|
725
|
+
command = [arg, *args]
|
726
|
+
|
727
|
+
env = kws.fetch(:env){ {} }
|
728
|
+
error = kws.fetch(:error){ true }
|
729
|
+
quiet = kws.fetch(:quiet){ false }
|
730
|
+
stdin = kws.fetch(:stdin){ '' }
|
731
|
+
|
732
|
+
env.transform_keys!(&:to_s)
|
733
|
+
env.transform_values!(&:to_s)
|
734
|
+
|
735
|
+
pid = nil
|
736
|
+
status = nil
|
737
|
+
stdout = nil
|
738
|
+
stderr = nil
|
739
|
+
|
740
|
+
Tempfile.open do |i|
|
741
|
+
i.write(stdin)
|
742
|
+
i.flush
|
703
743
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
744
|
+
Tempfile.open do |o|
|
745
|
+
Tempfile.open do |e|
|
746
|
+
redirects = {:in => i.path, :out => o.path, :err => e.path}
|
747
|
+
|
748
|
+
pid = Process.spawn(env, *command, redirects)
|
749
|
+
|
750
|
+
Process.wait(pid)
|
751
|
+
|
752
|
+
status = $?.exitstatus
|
753
|
+
|
754
|
+
stdout = IO.binread(o.path)
|
755
|
+
stderr = IO.binread(e.path)
|
756
|
+
end
|
757
|
+
end
|
708
758
|
end
|
709
759
|
|
710
|
-
|
760
|
+
unless status == 0
|
761
|
+
unless kws[:quiet] == true
|
762
|
+
log(:error, "#{ command.join(' ') } ###===>>> #{ status }\nSTDOUT:\n#{ stdout }\n\STDERR:\n#{ stderr }")
|
763
|
+
exit(status)
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
{command:, pid:, env:, status:, stdin:, stdout:, stderr:}
|
711
768
|
end
|
712
769
|
end
|
713
770
|
|
714
771
|
#
|
715
772
|
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)
|
773
|
+
attr_reader :pid
|
774
|
+
|
775
|
+
def initialize(cli:)
|
730
776
|
@cli = cli
|
731
|
-
end
|
732
777
|
|
733
|
-
|
734
|
-
|
778
|
+
@env = @cli.env
|
779
|
+
@directory = @cli.directory
|
780
|
+
@rails_root = @cli.rails_root
|
781
|
+
@parallel = @cli.parallel
|
782
|
+
@uuid = @cli.uuid
|
783
|
+
|
784
|
+
@thread = nil
|
785
|
+
@pid = nil
|
735
786
|
end
|
736
787
|
|
737
|
-
|
738
|
-
|
739
|
-
|
788
|
+
def start!(port:)
|
789
|
+
system("#{ version_command } >/dev/null 2>&1") ||
|
790
|
+
abort("app fails to load via: #{ version_command }")
|
740
791
|
|
741
|
-
|
742
|
-
@directory = @cli.directory
|
743
|
-
@rails_root = @cli.rails_root
|
744
|
-
@parallel = @cli.parallel
|
792
|
+
q = Queue.new
|
745
793
|
|
746
|
-
|
747
|
-
@pumactl = "bundle exec pumactl"
|
794
|
+
cmd = start_command_for(port)
|
748
795
|
|
749
|
-
|
750
|
-
@statefile = @cli.relative_path(File.join(@directory, ".puma-state.txt"), :from => @cli.rails_root)
|
751
|
-
end
|
796
|
+
log = './tmp/rails_build_server.log'
|
752
797
|
|
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
|
798
|
+
@cli.log(:info, "server: #{ cmd } > #{ log } 2>&1")
|
799
|
+
|
800
|
+
@thread = Thread.new do
|
801
|
+
Thread.current.abort_on_exception = true
|
802
|
+
pipe = IO.popen("#{ cmd } > #{ log } 2>&1")
|
803
|
+
q.push(pipe.pid)
|
767
804
|
end
|
768
805
|
|
769
|
-
|
806
|
+
@pid = q.pop
|
807
|
+
|
808
|
+
@cli.log(:info, "pid: #{ @pid }")
|
809
|
+
|
810
|
+
@assassin = Assassin.ate(@pid)
|
811
|
+
|
812
|
+
at_exit{ stop! }
|
813
|
+
end
|
814
|
+
|
815
|
+
def version_command
|
816
|
+
cmd_for(
|
770
817
|
%W[
|
771
|
-
|
772
|
-
|
773
|
-
--state=#{ @statefile }
|
774
|
-
stop
|
775
|
-
].join(' ').strip
|
776
|
-
end
|
818
|
+
RAILS_ENV=#{ @env }
|
819
|
+
DISABLE_SPRING=true
|
777
820
|
|
778
|
-
|
779
|
-
|
780
|
-
|
821
|
+
rails --version
|
822
|
+
]
|
823
|
+
)
|
781
824
|
end
|
782
825
|
|
783
|
-
|
784
|
-
|
785
|
-
|
826
|
+
def start_command_for(port)
|
827
|
+
cmd_for(
|
828
|
+
%W[
|
829
|
+
RAILS_ENV=#{ @env }
|
830
|
+
DISABLE_SPRING=true
|
786
831
|
|
787
|
-
|
788
|
-
@directory = @cli.directory
|
789
|
-
@rails_root = @cli.rails_root
|
790
|
-
@parallel = @cli.parallel
|
832
|
+
RAILS_BUILD=#{ @uuid }
|
791
833
|
|
792
|
-
|
793
|
-
|
834
|
+
RAILS_SERVE_STATIC_FILES=true
|
835
|
+
RAILS_LOG_TO_STDOUT=true
|
836
|
+
WEB_CONCURRENCY=#{ @parallel.to_s }
|
794
837
|
|
795
|
-
|
796
|
-
"#{ @passenger } start --daemonize --environment #{ @env } --port #{ port } --max-pool-size #{ @parallel }"
|
797
|
-
end
|
838
|
+
rails server
|
798
839
|
|
799
|
-
|
800
|
-
|
801
|
-
|
840
|
+
--environment=#{ @env }
|
841
|
+
--port=#{ port }
|
842
|
+
--binding=0.0.0.0
|
843
|
+
--log-to-stdout
|
844
|
+
]
|
845
|
+
)
|
846
|
+
end
|
802
847
|
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
FileUtils.rm_rf("#{ @rails_root }/tmp/pids/passenger.#{ port }.pid")
|
807
|
-
end
|
848
|
+
def cmd_for(arg, *args)
|
849
|
+
[arg, *args].flatten.compact.join(' ').squeeze(' ').strip
|
850
|
+
end
|
808
851
|
|
809
|
-
|
852
|
+
def stop!
|
853
|
+
kill!(@pid)
|
854
|
+
@thread.kill
|
855
|
+
@cli.log(:info, "stopped: #{ @pid }")
|
856
|
+
end
|
857
|
+
|
858
|
+
def kill!(pid)
|
859
|
+
42.times do
|
810
860
|
begin
|
811
|
-
|
812
|
-
|
813
|
-
|
861
|
+
Process.kill(0, pid)
|
862
|
+
return(true)
|
863
|
+
rescue Object => e
|
864
|
+
if e.is_a?(Errno::ESRCH)
|
865
|
+
Process.kill(-15, pid) rescue nil
|
866
|
+
sleep(rand + rand)
|
867
|
+
Process.kill(-9, pid) rescue nil
|
868
|
+
end
|
814
869
|
end
|
870
|
+
sleep(0.42 + rand)
|
815
871
|
end
|
872
|
+
return(false)
|
816
873
|
end
|
817
874
|
end
|
818
875
|
end
|
876
|
+
|
877
|
+
END {
|
878
|
+
require_relative '../lib/rails_build.rb'
|
879
|
+
|
880
|
+
STDOUT.sync = true
|
881
|
+
STDERR.sync = true
|
882
|
+
|
883
|
+
RailsBuild::CLI.run!
|
884
|
+
}
|
885
|
+
|
886
|
+
__END__
|
887
|
+
<<~________
|
888
|
+
|
889
|
+
this file should to enumerate all the urls you'd like to build
|
890
|
+
|
891
|
+
the contents of your ./public directory, and any assets, are automaticaly included
|
892
|
+
|
893
|
+
therefore you need only declare which dynamic urls, that is to say, 'routes'
|
894
|
+
|
895
|
+
you would like included in your build
|
896
|
+
|
897
|
+
it is not loaded except during build time, and will not affect your normal rails app in any way
|
898
|
+
|
899
|
+
________
|
900
|
+
|
901
|
+
|
902
|
+
RailsBuild.configure do |config|
|
903
|
+
|
904
|
+
# most of the time you are going to want your route included, which will
|
905
|
+
# translate into an ./index.html being output in the build
|
906
|
+
#
|
907
|
+
|
908
|
+
config.urls << '/'
|
909
|
+
|
910
|
+
# include any/all additional routes youd' like built thusly
|
911
|
+
#
|
912
|
+
|
913
|
+
Post.each do |post|
|
914
|
+
config.urls << "/posts/#{ post.id }"
|
915
|
+
end
|
916
|
+
|
917
|
+
# thats it! - now just run `rails_build` and you are GTG
|
918
|
+
|
919
|
+
end
|