hotreload 0.1.0 → 0.2.0
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/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
|