hotreload 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/config.rb +6 -1
- data/lib/hotreload.rb +214 -49
- data/lib/main.rb +19 -3
- data/lib/server.rb +45 -7
- data/lib/signer.rb +36 -3
- data/lib/tool.rb +10 -0
- data/lib/xcactivitylog/class_name_resolver.rb +26 -0
- data/lib/xcactivitylog/compile_command.rb +67 -0
- data/lib/xcactivitylog/deserializer.rb +45 -0
- data/lib/xcactivitylog/dvtdocumentlocation.rb +17 -0
- data/lib/xcactivitylog/keyword_struct.rb +10 -0
- data/lib/xcactivitylog/log_deserializer.rb +17 -0
- data/lib/xcactivitylog/tokenizer.rb +82 -0
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: db715d301b4fc5e584009030dadb851473c9768c86d4d12f2012c9f8beb63cae
|
4
|
+
data.tar.gz: bebbea7e8157eaac6f99aa6fd93a7ca68279c70cd33ea1d808fd85b80d5eec67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6feed1c4c5a73936fe6ffe7a8e455dd131b6373efda23c5cf53ae269abecffa303b0d9d871b1972fda2c82ae7101947514ee4c4b516930d5e4974564f98b5ff
|
7
|
+
data.tar.gz: 3f43e6f529dbe829e1893347bceeaa225bffb5f14f9e578a366c78e7c1bffd20afbe06b81b50a0e4edfb4fe37e631f1db5a94c84d43ca0a96396ff0260306d67
|
data/lib/config.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Config
|
2
2
|
PORT = 8898
|
3
|
-
HOST = '127.0.0.1'
|
3
|
+
# HOST = '127.0.0.1'
|
4
4
|
CLIENT_NUM = 1
|
5
5
|
HOTRELOAD_KEY = 'bvijkijyhbt73jdk83ljbbzcfbbvvq'
|
6
6
|
end
|
@@ -23,5 +23,10 @@ module Command
|
|
23
23
|
InjectionEval = 10
|
24
24
|
InjectionVaccineSettingChanged = 11
|
25
25
|
|
26
|
+
InjectionTransferFile = 12
|
27
|
+
HRCommandTransferFileNameFinished = 13
|
28
|
+
HRCommandTransferFileSizeFinished = 14
|
29
|
+
HRCommandTransferFileFinished = 15
|
30
|
+
|
26
31
|
InjectionEOF = ~0
|
27
32
|
end
|
data/lib/hotreload.rb
CHANGED
@@ -5,6 +5,8 @@ require 'server'
|
|
5
5
|
require 'config'
|
6
6
|
require 'file_watcher'
|
7
7
|
require 'signer'
|
8
|
+
require 'xcactivitylog/compile_command'
|
9
|
+
require 'tool'
|
8
10
|
include Config
|
9
11
|
include Command
|
10
12
|
|
@@ -14,74 +16,90 @@ class HotReload
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def initialize
|
17
|
-
@
|
19
|
+
@project_injected = Hash.new
|
18
20
|
@pending = []
|
21
|
+
@project_path = ""
|
22
|
+
@project_file = ""
|
23
|
+
@server = nil
|
24
|
+
@last_injected = ""
|
25
|
+
@hot_reload_id = 0
|
26
|
+
@hot_reload_tmp_path = ""
|
27
|
+
@compile_class = Hash.new
|
28
|
+
@xcode_dev_path = "/Applications/Xcode.app/Contents/Developer"
|
29
|
+
|
19
30
|
end
|
20
31
|
|
21
32
|
def startServer(projectpath)
|
22
|
-
@
|
33
|
+
@project_path = File.expand_path("..", projectpath)
|
23
34
|
@server = Server.new
|
24
35
|
@server.run
|
25
36
|
|
26
|
-
#
|
27
|
-
@
|
37
|
+
# create dir
|
38
|
+
@hot_reload_tmp_path = "#{@project_path}/HotReload-tmp"
|
39
|
+
if !Dir.exist?(@hot_reload_tmp_path)
|
40
|
+
Dir.mkdir(@hot_reload_tmp_path)
|
41
|
+
end
|
28
42
|
|
29
43
|
# tell client the project being watched
|
30
|
-
|
31
|
-
files_sorted_by_time = Dir[
|
32
|
-
|
33
|
-
if
|
34
|
-
|
35
|
-
files_sorted_by_time = Dir[
|
36
|
-
|
44
|
+
@project_file = "#{@project_path}/*.xcworkspace"
|
45
|
+
files_sorted_by_time = Dir[@project_file].sort_by{ |f| File.mtime(f) }
|
46
|
+
@project_file = files_sorted_by_time[0]
|
47
|
+
if @project_file == nil
|
48
|
+
@project_file = "#{@project_path}/*.xcodeproj"
|
49
|
+
files_sorted_by_time = Dir[@project_file].sort_by{ |f| File.mtime(f) }
|
50
|
+
@project_file = files_sorted_by_time[0]
|
37
51
|
end
|
38
52
|
|
39
|
-
if
|
53
|
+
if read_message != Config::HOTRELOAD_KEY
|
40
54
|
puts "not the validate client"
|
41
55
|
return
|
42
56
|
end
|
43
57
|
|
44
58
|
# client spcific data for building
|
45
|
-
frameworkPath = readMessage
|
46
|
-
arch = readMessage
|
47
|
-
|
48
|
-
@lastInjected = @projectInjected[projectfile]
|
49
|
-
if @lastInjected == nil
|
50
|
-
@lastInjected = Hash.new
|
51
|
-
@projectInjected[projectfile] = @lastInjected
|
52
|
-
end
|
59
|
+
# frameworkPath = readMessage
|
60
|
+
# arch = readMessage
|
53
61
|
|
54
|
-
|
55
|
-
if
|
56
|
-
|
57
|
-
@
|
58
|
-
if File.extname(key) != ".storyboard" && File.extname(key) != "xib" && File.mtime(key).tv_sec > executable
|
59
|
-
inject(key)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
else
|
63
|
-
return
|
62
|
+
@last_injected = @project_injected[@project_file]
|
63
|
+
if @last_injected == nil
|
64
|
+
@last_injected = Hash.new
|
65
|
+
@project_injected[@project_file] = @last_injected
|
64
66
|
end
|
65
67
|
|
66
|
-
|
68
|
+
# listen_command
|
67
69
|
|
68
|
-
|
70
|
+
set_project(@project_file)
|
69
71
|
|
70
72
|
@watcher = nil
|
71
73
|
|
72
74
|
end
|
73
75
|
|
74
|
-
def
|
75
|
-
@server.
|
76
|
+
def read_int
|
77
|
+
@server.read_int
|
76
78
|
end
|
77
79
|
|
78
|
-
def
|
79
|
-
@server.
|
80
|
+
def read_message
|
81
|
+
@server.read_message
|
80
82
|
end
|
81
83
|
|
82
84
|
def inject(source)
|
83
85
|
Thread.new do
|
84
|
-
|
86
|
+
# rebuild
|
87
|
+
dylib = build(source)
|
88
|
+
if !dylib
|
89
|
+
puts "build failed"
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
symbol_file = get_symbol_file
|
94
|
+
|
95
|
+
@server.send_command(Command::InjectionTransferFile, nil)
|
96
|
+
|
97
|
+
puts "start transfer file"
|
98
|
+
file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}"
|
99
|
+
@server.send_file(dylib)
|
100
|
+
@server.send_file("#{file_tmp}.o")
|
101
|
+
@server.send_file(symbol_file)
|
102
|
+
puts "finish transfer file"
|
85
103
|
end
|
86
104
|
end
|
87
105
|
|
@@ -89,9 +107,9 @@ class HotReload
|
|
89
107
|
now = Time.now.to_i
|
90
108
|
changeFiles.each do |file|
|
91
109
|
unless @pending.include?(file)
|
92
|
-
time = @
|
110
|
+
time = @last_injected[file]
|
93
111
|
if time == nil || now > time
|
94
|
-
@
|
112
|
+
@last_injected[file] = now
|
95
113
|
@pending.push(file)
|
96
114
|
end
|
97
115
|
end
|
@@ -104,30 +122,28 @@ class HotReload
|
|
104
122
|
|
105
123
|
end
|
106
124
|
|
107
|
-
def
|
108
|
-
@server.
|
109
|
-
|
110
|
-
@watcher = FileWatcher.new(@projectPath)
|
111
|
-
# @watcher = FileWatcher.new('/Users/shantj/Documents/workspace/third/injection') TODO
|
125
|
+
def set_project(project_file)
|
126
|
+
@server.send_command(Command::InjectionProject, project_file)
|
127
|
+
@watcher = FileWatcher.new(@project_path)
|
112
128
|
@watcher.startWatcher do |file|
|
113
129
|
changeFiles(file)
|
114
130
|
end
|
115
131
|
end
|
116
132
|
|
117
|
-
def
|
133
|
+
def listen_command
|
118
134
|
Thread.new do
|
119
|
-
while (command =
|
135
|
+
while (command = read_int) != Command::InjectionEOF
|
120
136
|
case command
|
121
137
|
when Command::InjectionComplete
|
122
138
|
puts "Injection Complete"
|
123
139
|
when Command::InjectionPause
|
124
140
|
when Command::InjectionSign
|
125
|
-
dylib =
|
141
|
+
dylib = read_message
|
126
142
|
signer = Signer.new
|
127
143
|
ret = signer.codeSignDylib(dylib)
|
128
|
-
@server.
|
144
|
+
@server.send_command(Command::InjectionSigned, ret ? '1' : '0')
|
129
145
|
when Command::InjectionError
|
130
|
-
puts "Injection error: #{
|
146
|
+
puts "Injection error: #{read_message}"
|
131
147
|
else
|
132
148
|
puts "InjectionServer: Unexpected case #{command}"
|
133
149
|
exit
|
@@ -137,4 +153,153 @@ class HotReload
|
|
137
153
|
end
|
138
154
|
end
|
139
155
|
|
156
|
+
# rebuild file
|
157
|
+
def build(file)
|
158
|
+
# project_model : project_file , build_Log
|
159
|
+
project_model = get_project_model(file)
|
160
|
+
project_file = project_model["project_file"]
|
161
|
+
build_log_dir = project_model["build_log"]
|
162
|
+
puts "project_file=#{project_file}, build_log=#{build_log_dir}"
|
163
|
+
|
164
|
+
@hot_reload_id += 1
|
165
|
+
file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}"
|
166
|
+
log_file = "#{file_tmp}.log"
|
167
|
+
|
168
|
+
compile_model = @compile_class[file]
|
169
|
+
if !compile_model
|
170
|
+
compile_model = get_compile_model(build_log_dir, file, file_tmp)
|
171
|
+
if !compile_model
|
172
|
+
puts
|
173
|
+
"Could not locate compile command for #{file} (HotReload does not work with Whole Module Optimization. There are also restrictions on characters allowed in paths. All paths are also case sensitive is another thing to check.)"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
puts "Compiling #{file}"
|
178
|
+
project_dir_temp = Tool::escaping(@project_path, "$", "\\$0")
|
179
|
+
compile_command = compile_model["compile_command"]
|
180
|
+
command = "(cd #{project_dir_temp}) && #{compile_command} -o #{file_tmp}.o >#{log_file} 2>&1"
|
181
|
+
ret = system(command)
|
182
|
+
if !ret
|
183
|
+
@compile_class.delete(file)
|
184
|
+
puts "Re-compilation failed (#{project_dir_temp}/command.sh)\n#{File.read(log_file)})"
|
185
|
+
end
|
186
|
+
|
187
|
+
# save in memory
|
188
|
+
@compile_class[file] = compile_model
|
189
|
+
|
190
|
+
# link result object file to create dylib
|
191
|
+
puts "Creating dylib"
|
192
|
+
regex = "\\s*(\\S+?\\.xctoolchain)"
|
193
|
+
tool_chain = nil
|
194
|
+
regex_result = compile_command.match(regex)
|
195
|
+
if regex_result
|
196
|
+
tool_chain = regex_result.captures[0]
|
197
|
+
end
|
198
|
+
if !tool_chain
|
199
|
+
tool_chain = "#{@xcode_dev_path}/Toolchains/XcodeDefault.xctoolchain"
|
200
|
+
end
|
201
|
+
|
202
|
+
os_specific = ""
|
203
|
+
cpu_arch = "arm64"
|
204
|
+
is_real_device = true
|
205
|
+
if compile_command.include?("iPhoneSimulator.platform")
|
206
|
+
os_specific = "-isysroot #{@xcode_dev_path}/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -mios-simulator-version-min=9.0 -L#{tool_chain}/usr/lib/swift/iphonesimulator -undefined dynamic_lookup"
|
207
|
+
cpu_arch = "x86_64"
|
208
|
+
is_real_device = false
|
209
|
+
elsif compile_command.include?("iPhoneOS.platform")
|
210
|
+
os_specific = "-isysroot #{@xcode_dev_path}/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -mios-simulator-version-min=9.0 -L#{tool_chain}/usr/lib/swift/iphonesimulator -undefined dynamic_lookup"
|
211
|
+
cpu_arch = "arm64"
|
212
|
+
end
|
213
|
+
|
214
|
+
link_command = "#{@xcode_dev_path}/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch \"#{cpu_arch}\" -bundle #{os_specific} -dead_strip -Xlinker -objc_abi_version -Xlinker 2 -fobjc-arc #{file_tmp}.o -o #{file_tmp}.dylib >>#{log_file} 2>&1"
|
215
|
+
ret = system(link_command)
|
216
|
+
if !ret
|
217
|
+
puts "Link failed, check #{@hot_reload_tmp_path}/command.sh \n #{log_file}"
|
218
|
+
end
|
219
|
+
|
220
|
+
puts "codesign dylib"
|
221
|
+
codesigner = Signer.new()
|
222
|
+
codesigner.pre_codesign(build_log_dir, is_real_device)
|
223
|
+
ret = codesigner.codeSignDylib("#{file_tmp}.dylib")
|
224
|
+
if !ret
|
225
|
+
puts "codesign failed"
|
226
|
+
return
|
227
|
+
else
|
228
|
+
puts "codesign success \n #{file_tmp}.dylib"
|
229
|
+
return "#{file_tmp}.dylib"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# get project file and xcode build log path
|
234
|
+
def get_project_model(file)
|
235
|
+
if !file.start_with?('/')
|
236
|
+
puts "file is not validate: #{file}"
|
237
|
+
return
|
238
|
+
end
|
239
|
+
|
240
|
+
# default deriveData path
|
241
|
+
derive_data_path = File.expand_path("~/Library/Developer/Xcode/DerivedData")
|
242
|
+
build_log = build_log_path(@project_file, derive_data_path)
|
243
|
+
|
244
|
+
project_model = Hash["project_file" => @project_file, "build_log" => build_log]
|
245
|
+
end
|
246
|
+
|
247
|
+
# get xcode build log
|
248
|
+
def build_log_path(project_file, derive_data_path)
|
249
|
+
# file = "/path/to/xyz.mp4"
|
250
|
+
ext = File.extname(project_file) # => ".mp4"
|
251
|
+
name = File.basename(project_file, ext) # => "xyz"
|
252
|
+
prefix = "#{name}"+"-"
|
253
|
+
|
254
|
+
build_log_array = []
|
255
|
+
Dir.entries(derive_data_path).each do |sub|
|
256
|
+
if sub.length <= prefix.length
|
257
|
+
next
|
258
|
+
end
|
259
|
+
|
260
|
+
if sub.start_with?(prefix)
|
261
|
+
buildLogDir = derive_data_path + "/" + sub + "/Logs/Build"
|
262
|
+
if Dir.exist?(buildLogDir)
|
263
|
+
build_log_array.push(buildLogDir)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
log_sort = build_log_array.sort_by{ |f| File.mtime(f) }
|
269
|
+
log_sort[0]
|
270
|
+
end
|
271
|
+
|
272
|
+
# get compile command and changed file
|
273
|
+
def get_compile_model(build_log_dir, file, tmp_file)
|
274
|
+
if !File.file?(file)
|
275
|
+
return
|
276
|
+
end
|
277
|
+
|
278
|
+
# find build command
|
279
|
+
CompileCommand.new().getCompileCommand(build_log_dir, file, tmp_file)
|
280
|
+
|
281
|
+
compile_command_file = "#{tmp_file}.sh"
|
282
|
+
if !File.exist?(compile_command_file)
|
283
|
+
puts "compile command parse error"
|
284
|
+
end
|
285
|
+
|
286
|
+
compile_command = File.read("#{tmp_file}.sh")
|
287
|
+
compile_command = compile_command.split(" -o ")[0] + " "
|
288
|
+
|
289
|
+
compile_model = Hash["compile_command" => compile_command, "file" => file]
|
290
|
+
end
|
291
|
+
|
292
|
+
# get class symbols
|
293
|
+
def get_symbol_file
|
294
|
+
file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}"
|
295
|
+
|
296
|
+
symbol_command = "#{@xcode_dev_path}/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm #{file_tmp}.o | grep -E ' S _OBJC_CLASS_\\$_| _(_T0|\\$S|\\$s).*CN$' | awk '{print $3}' >#{file_tmp}.classes"
|
297
|
+
ret = system(symbol_command)
|
298
|
+
if !ret
|
299
|
+
puts "Could not list class symbols"
|
300
|
+
end
|
301
|
+
|
302
|
+
class_file = "#{file_tmp}.classes"
|
303
|
+
end
|
304
|
+
|
140
305
|
end
|
data/lib/main.rb
CHANGED
@@ -1,8 +1,24 @@
|
|
1
|
-
#!/usr/bin/
|
1
|
+
#!/usr/bin/ruby -w
|
2
2
|
# -*- coding : utf-8 -*-
|
3
|
+
#
|
3
4
|
require_relative 'hotreload'
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
# require 'open3'
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# codesign_identity = "-"
|
10
|
+
# stdout, stdeerr, status = Open3.capture3("codesign -vv -d /Users/shijishan/Library/Developer/Xcode/DerivedData/HotReloadClient-amhluyfrvrkwscccomzodygknuar/Build/Products/Debug-iphoneos/HotReloadClient_Example.app")
|
11
|
+
# array = stdeerr.split("\n")
|
12
|
+
# array.each do |item|
|
13
|
+
# if item.include?("Authority")
|
14
|
+
# codesign_identity = item
|
15
|
+
# break
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# puts codesign_identity
|
7
20
|
|
8
21
|
|
22
|
+
|
23
|
+
project_path = "/Users/***/Documents/workspace/third/HotReloadClient/Example/Pods"
|
24
|
+
# HotReload.new().startServer(project_path)
|
data/lib/server.rb
CHANGED
@@ -9,7 +9,8 @@ include Config
|
|
9
9
|
class Server
|
10
10
|
def run
|
11
11
|
@server = Socket.new(AF_INET, SOCK_STREAM, 0)
|
12
|
-
|
12
|
+
ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
|
13
|
+
sockaddr = Socket.sockaddr_in(Config::PORT, ip.ip_address)
|
13
14
|
@server.bind(sockaddr)
|
14
15
|
@server.listen(1)
|
15
16
|
@client, _ = @server.accept
|
@@ -27,7 +28,7 @@ class Server
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
+
def read_int
|
31
32
|
raise "client not connect #{@host}:#{@port}, please check" if @client == nil
|
32
33
|
|
33
34
|
begin
|
@@ -41,11 +42,11 @@ class Server
|
|
41
42
|
msgInt
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
+
def read_message
|
45
46
|
raise "client not connect #{@host}:#{@port}, please check" if @client == nil
|
46
47
|
|
47
48
|
begin
|
48
|
-
len =
|
49
|
+
len = read_int
|
49
50
|
if len == ~0
|
50
51
|
return nil
|
51
52
|
end
|
@@ -59,7 +60,7 @@ class Server
|
|
59
60
|
message
|
60
61
|
end
|
61
62
|
|
62
|
-
def
|
63
|
+
def send_message(msg)
|
63
64
|
raise "client not connect #{@host}:#{@port}, please check" if @client == nil
|
64
65
|
|
65
66
|
begin
|
@@ -73,12 +74,14 @@ class Server
|
|
73
74
|
true
|
74
75
|
end
|
75
76
|
|
76
|
-
def
|
77
|
+
def send_command(command, msg)
|
77
78
|
raise "client not connect #{@host}:#{@port}, please check" if @client == nil
|
78
79
|
|
79
80
|
begin
|
80
81
|
@client.write([command].pack("L"))
|
81
|
-
|
82
|
+
if msg
|
83
|
+
send_message(msg)
|
84
|
+
end
|
82
85
|
rescue StandardError => e
|
83
86
|
puts e.message
|
84
87
|
puts e.backtrace.inspect
|
@@ -87,4 +90,39 @@ class Server
|
|
87
90
|
true
|
88
91
|
end
|
89
92
|
|
93
|
+
def send_file(file)
|
94
|
+
raise "client not connect #{@host}:#{@port}, please check" if @client == nil
|
95
|
+
|
96
|
+
begin
|
97
|
+
send_message(file)
|
98
|
+
|
99
|
+
command = read_int
|
100
|
+
if command != HRCommandTransferFileNameFinished
|
101
|
+
puts "send file name failed, #{file}"
|
102
|
+
end
|
103
|
+
|
104
|
+
size = File.size(file)
|
105
|
+
send_message("#{size}")
|
106
|
+
command = read_int
|
107
|
+
if command != HRCommandTransferFileSizeFinished
|
108
|
+
puts "send file size failed, #{size}"
|
109
|
+
end
|
110
|
+
|
111
|
+
File.open(file, "r") do |f|
|
112
|
+
f.each_line do |line|
|
113
|
+
@client.write(line)
|
114
|
+
puts "#{line.bytesize}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
command = read_int
|
119
|
+
if command != HRCommandTransferFileFinished
|
120
|
+
puts "send file failed, #{file}"
|
121
|
+
end
|
122
|
+
rescue StandardError => e
|
123
|
+
puts e.message
|
124
|
+
puts e.backtrace.inspect
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
90
128
|
end
|
data/lib/signer.rb
CHANGED
@@ -1,4 +1,39 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
1
3
|
class Signer
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@codesign_identity = "-"
|
7
|
+
end
|
8
|
+
|
9
|
+
# pre codesign
|
10
|
+
def pre_codesign(build_log_dir, is_real_device)
|
11
|
+
build_folder = is_real_device ? "Debug-iphoneos" : "Debug-iphonesimulator"
|
12
|
+
exe_path = build_log_dir.split("/")[0..-3].join("/") + "/Build/Products/#{build_folder}"
|
13
|
+
exe_path = "#{exe_path}/*.app"
|
14
|
+
exe_path = Dir[exe_path].sort!{ |x,y| File.mtime(y) <=> File.mtime(x) }[0]
|
15
|
+
if File.exist?(exe_path)
|
16
|
+
parse_codesign_identity(exe_path)
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_codesign_identity(exe_path)
|
22
|
+
codesign_identity = "-"
|
23
|
+
stdout, stdeerr, status = Open3.capture3("codesign -vv -d #{exe_path}")
|
24
|
+
array = stdeerr.split("\n")
|
25
|
+
array.each do |item|
|
26
|
+
if item.include?("Authority=")
|
27
|
+
start = "Authority="
|
28
|
+
codesign_identity = item[start.length, item.length]
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
@codesign_identity = codesign_identity
|
35
|
+
end
|
36
|
+
|
2
37
|
# sign the dynamic library
|
3
38
|
#
|
4
39
|
# @param dylib the dylib's path
|
@@ -6,9 +41,7 @@ class Signer
|
|
6
41
|
# @return true or false
|
7
42
|
#
|
8
43
|
def codeSignDylib(dylib)
|
9
|
-
command = "export CODESIGN_ALLOCATE=/Applications/Xcode.app"
|
10
|
-
"/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; "
|
11
|
-
"/usr/bin/codesign --force -s '-' '#{dylib}'"
|
44
|
+
command = "export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; /usr/bin/codesign --force -s '#{@codesign_identity}' '#{dylib}'"
|
12
45
|
system(command)
|
13
46
|
end
|
14
47
|
end
|
data/lib/tool.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module XcodeResultBundleProcessor
|
2
|
+
module SLF0
|
3
|
+
module ClassNameResolver
|
4
|
+
|
5
|
+
ResolvedClassName = Struct.new(:class_name)
|
6
|
+
|
7
|
+
def self.resolve_class_names(tokens)
|
8
|
+
class_names = []
|
9
|
+
|
10
|
+
Enumerator.new do |enumerator|
|
11
|
+
tokens.each do |token|
|
12
|
+
if token.is_a?(Tokenizer::ClassName)
|
13
|
+
class_names << token.class_name
|
14
|
+
elsif token.is_a?(Tokenizer::ClassNameRef)
|
15
|
+
raise "Invalid ClassNameRef to class index #{token.class_name_id}" if token.class_name_id > class_names.length
|
16
|
+
class_name = class_names[token.class_name_id - 1]
|
17
|
+
enumerator.yield(ResolvedClassName.new(class_name))
|
18
|
+
else
|
19
|
+
enumerator.yield(token)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- coding : utf-8 -*-
|
3
|
+
|
4
|
+
require 'zlib'
|
5
|
+
require_relative 'log_deserializer'
|
6
|
+
|
7
|
+
class CompileCommand
|
8
|
+
|
9
|
+
def getCompileCommand(xcactivitylog, compile_file, output_file)
|
10
|
+
ret = false
|
11
|
+
if File.directory?(xcactivitylog)
|
12
|
+
path = "#{xcactivitylog}/*.xcactivitylog"
|
13
|
+
files_sorted_by_time = Dir[path].sort!{ |x,y| File.mtime(y) <=> File.mtime(x) }
|
14
|
+
files_sorted_by_time.each do |file|
|
15
|
+
ret = getCompileCommandFromLog(file, compile_file, output_file)
|
16
|
+
if ret
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
ret = getCompileCommandFromLog(xcactivitylog, compile_file, output_file)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def getCompileCommandFromLog(xcactivitylog, compile_file, output_file)
|
26
|
+
ext = File.extname(compile_file)
|
27
|
+
file = File.basename(compile_file, ext)
|
28
|
+
file = "#{file}.o"
|
29
|
+
|
30
|
+
output_file = "#{output_file}.sh"
|
31
|
+
|
32
|
+
# filter compile command
|
33
|
+
reg = ".*clang+.*Applications(.*o)"
|
34
|
+
|
35
|
+
# prase slf0 and filter the command
|
36
|
+
flag = false
|
37
|
+
text = XcodeResultBundleProcessor::Logdeserializer.deserialize_action_logs(xcactivitylog)
|
38
|
+
text.each do |item|
|
39
|
+
# puts item
|
40
|
+
# if item =~ reg && item =~ /#{file}/
|
41
|
+
if item =~ /#{file}/
|
42
|
+
content = item.match(reg)
|
43
|
+
if content
|
44
|
+
puts content
|
45
|
+
|
46
|
+
writeFile(output_file, content)
|
47
|
+
flag = true
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
flag
|
54
|
+
end
|
55
|
+
|
56
|
+
def writeFile(file, content)
|
57
|
+
aFile = File.new(file, "w+")
|
58
|
+
if aFile
|
59
|
+
aFile.syswrite(content)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# ARGV[0] xcactivitylog folder path or file path
|
65
|
+
# ARGV[1] compile file full path
|
66
|
+
# ARGV[2] out put file path
|
67
|
+
# CompileCommand.new().getCompileCommand(ARGV[0], ARGV[1], ARGV[2])
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'dvtdocumentlocation'
|
2
|
+
|
3
|
+
module XcodeResultBundleProcessor
|
4
|
+
module SLF0
|
5
|
+
module Deserializer
|
6
|
+
|
7
|
+
def self.deserialize(tokens)
|
8
|
+
|
9
|
+
if tokens.first.nil?
|
10
|
+
tokens.shift
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
|
14
|
+
self._assert_first_token_type(ClassNameResolver::ResolvedClassName, tokens)
|
15
|
+
|
16
|
+
resolved_class = tokens.shift
|
17
|
+
return nil if resolved_class.nil?
|
18
|
+
|
19
|
+
class_name = resolved_class.class_name
|
20
|
+
raise "Unsupported class #{class_name}" unless Model.const_defined?(class_name)
|
21
|
+
|
22
|
+
Model.const_get(class_name).deserialize(tokens)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.deserialize_list(tokens)
|
26
|
+
if tokens.first.nil?
|
27
|
+
tokens.shift
|
28
|
+
return []
|
29
|
+
end
|
30
|
+
|
31
|
+
self._assert_first_token_type(Tokenizer::ObjectList, tokens)
|
32
|
+
|
33
|
+
object_list_info = tokens.shift
|
34
|
+
|
35
|
+
object_list_info.mystery_number.times.map { Deserializer.deserialize(tokens) }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self._assert_first_token_type(expected_type, tokens)
|
41
|
+
raise "First token should be #{expected_type} but was a <#{tokens.first}>" unless tokens.first.is_a?(expected_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'keyword_struct'
|
2
|
+
module XcodeResultBundleProcessor
|
3
|
+
module SLF0
|
4
|
+
module Model
|
5
|
+
class DVTDocumentLocation < KeywordStruct.new(:document_url_string,
|
6
|
+
:timestamp)
|
7
|
+
|
8
|
+
def self.deserialize(tokens)
|
9
|
+
self.new(
|
10
|
+
document_url_string: tokens.shift,
|
11
|
+
timestamp: tokens.shift,
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'tokenizer'
|
2
|
+
require_relative 'class_name_resolver'
|
3
|
+
require_relative 'deserializer'
|
4
|
+
|
5
|
+
module XcodeResultBundleProcessor
|
6
|
+
module Logdeserializer
|
7
|
+
include XcodeResultBundleProcessor::SLF0::Tokenizer
|
8
|
+
def self.deserialize_action_logs(xcactivitylog)
|
9
|
+
|
10
|
+
File.open(xcactivitylog, "r") do |activity_log_io|
|
11
|
+
io = Zlib::GzipReader.new(activity_log_io)
|
12
|
+
tokens = XcodeResultBundleProcessor::SLF0::Tokenizer.read_token_stream(io)
|
13
|
+
tokens = XcodeResultBundleProcessor::SLF0::ClassNameResolver.resolve_class_names(tokens).to_a
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module XcodeResultBundleProcessor
|
2
|
+
module SLF0
|
3
|
+
module Tokenizer
|
4
|
+
Token = Struct.new(:int, :identifier)
|
5
|
+
ClassName = Struct.new(:class_name)
|
6
|
+
ClassNameRef = Struct.new(:class_name_id)
|
7
|
+
ObjectList = Struct.new(:mystery_number)
|
8
|
+
TOKEN_INT = '#'
|
9
|
+
TOKEN_CLASS_NAME = '%'
|
10
|
+
TOKEN_CLASS_NAME_REF = '@'
|
11
|
+
TOKEN_STRING = '"'
|
12
|
+
TOKEN_DOUBLE = '^'
|
13
|
+
OBJECT_LIST_NIL = '-'
|
14
|
+
OBJECT_LIST = '('
|
15
|
+
|
16
|
+
def self.read_token_stream(io)
|
17
|
+
raise 'Not an SLF0 file' unless self.valid_slf0_io?(io)
|
18
|
+
|
19
|
+
Enumerator.new do |enumerator|
|
20
|
+
until io.eof?
|
21
|
+
token = self.read_length_and_token_type(io)
|
22
|
+
|
23
|
+
case token.identifier
|
24
|
+
when TOKEN_INT
|
25
|
+
enumerator.yield(token.int)
|
26
|
+
when TOKEN_DOUBLE
|
27
|
+
enumerator.yield(token.int)
|
28
|
+
when TOKEN_STRING
|
29
|
+
enumerator.yield(io.read(token.int).gsub("\r", "\n"))
|
30
|
+
when TOKEN_CLASS_NAME
|
31
|
+
enumerator.yield(ClassName.new(io.read(token.int)))
|
32
|
+
when TOKEN_CLASS_NAME_REF
|
33
|
+
enumerator.yield(ClassNameRef.new(token.int))
|
34
|
+
when OBJECT_LIST
|
35
|
+
enumerator.yield(ObjectList.new(token.int))
|
36
|
+
when OBJECT_LIST_NIL
|
37
|
+
enumerator.yield(nil)
|
38
|
+
else
|
39
|
+
enumerator.yield(token)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.valid_slf0_io?(io)
|
47
|
+
return false if io.nil?
|
48
|
+
io.read(4) == 'SLF0'
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.read_length_and_token_type(io)
|
52
|
+
# The length appears as the start of the token as 0 or more ASCII decimal or hex digits until the token type marker
|
53
|
+
length_string = ''
|
54
|
+
until [TOKEN_INT, TOKEN_CLASS_NAME, TOKEN_CLASS_NAME_REF, TOKEN_STRING, TOKEN_DOUBLE, OBJECT_LIST_NIL, OBJECT_LIST].include?((c = io.readchar)) do
|
55
|
+
length_string << c
|
56
|
+
end
|
57
|
+
|
58
|
+
token = c
|
59
|
+
|
60
|
+
# TODO: Doubles are stored as hex strings. If it turns out the doubles contain useful information, implement
|
61
|
+
# logic to convert the hex string to doubles
|
62
|
+
if token == TOKEN_DOUBLE
|
63
|
+
length_string = ''
|
64
|
+
end
|
65
|
+
|
66
|
+
return Token.new(length_string.to_i, c)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def self._read_length_for_token(io, token_identifier)
|
72
|
+
# The length is an ASCII decimal string possibly preceded by dashes
|
73
|
+
length_string = ''
|
74
|
+
while (c = io.readchar) != token_identifier do
|
75
|
+
length_string << c unless c == '-' # Some strings lengths are preceded by 1 or more dashes, which we want to skip
|
76
|
+
end
|
77
|
+
|
78
|
+
length_string.to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hotreload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- shantj
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -81,6 +81,14 @@ files:
|
|
81
81
|
- lib/main.rb
|
82
82
|
- lib/server.rb
|
83
83
|
- lib/signer.rb
|
84
|
+
- lib/tool.rb
|
85
|
+
- lib/xcactivitylog/class_name_resolver.rb
|
86
|
+
- lib/xcactivitylog/compile_command.rb
|
87
|
+
- lib/xcactivitylog/deserializer.rb
|
88
|
+
- lib/xcactivitylog/dvtdocumentlocation.rb
|
89
|
+
- lib/xcactivitylog/keyword_struct.rb
|
90
|
+
- lib/xcactivitylog/log_deserializer.rb
|
91
|
+
- lib/xcactivitylog/tokenizer.rb
|
84
92
|
homepage: https://github.com/shantj/hotreload.git
|
85
93
|
licenses:
|
86
94
|
- MIT
|
@@ -100,8 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
108
|
- !ruby/object:Gem::Version
|
101
109
|
version: '0'
|
102
110
|
requirements: []
|
103
|
-
|
104
|
-
rubygems_version: 2.6.14
|
111
|
+
rubygems_version: 3.0.4
|
105
112
|
signing_key:
|
106
113
|
specification_version: 4
|
107
114
|
summary: hot reload changed files
|