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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c06e1f69e44e90bdb80283e3d314cedf32b43e1c
4
- data.tar.gz: 71b80d3601f4fb729d2d8b73c30a6d445bfc605d
2
+ SHA256:
3
+ metadata.gz: db715d301b4fc5e584009030dadb851473c9768c86d4d12f2012c9f8beb63cae
4
+ data.tar.gz: bebbea7e8157eaac6f99aa6fd93a7ca68279c70cd33ea1d808fd85b80d5eec67
5
5
  SHA512:
6
- metadata.gz: c7d524e36dd65e25eb4ef488225bfc86c8a05b64f485194d000ffd4f73f5f17db8072993c4954953fe1ecfd9e8f1e5bf141f0d8152c9a1ae34de5f291b776686
7
- data.tar.gz: 5ad1d92fafe72204fcb20ce1c4030f4f858795d814e251b11d5f99162b4a6ec75215945adb8696c4d0d501b3507622ef4ea918253068293d0d16eb68a5277747
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
- @projectInjected = Hash.new
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
- @projectPath = File.expand_path("..", projectpath)
33
+ @project_path = File.expand_path("..", projectpath)
23
34
  @server = Server.new
24
35
  @server.run
25
36
 
26
- # get current dir
27
- @server.sendMessage(@projectPath)
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
- projectfile = "#{@projectPath}/*.xcworkspace"
31
- files_sorted_by_time = Dir[projectfile].sort_by{ |f| File.mtime(f) }
32
- projectfile = files_sorted_by_time[0]
33
- if projectfile == nil
34
- projectfile = "#{@projectPath}/*.xcodeproj"
35
- files_sorted_by_time = Dir[projectfile].sort_by{ |f| File.mtime(f) }
36
- projectfile = files_sorted_by_time[0]
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 readMessage != Config::HOTRELOAD_KEY
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
- executable = readMessage
55
- if executable != nil
56
- executableBuild = File.mtime(executable).tv_sec
57
- @lastInjected.each do |key, value|
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
- listenCommand
68
+ # listen_command
67
69
 
68
- setProject(projectfile)
70
+ set_project(@project_file)
69
71
 
70
72
  @watcher = nil
71
73
 
72
74
  end
73
75
 
74
- def readInt
75
- @server.readInt
76
+ def read_int
77
+ @server.read_int
76
78
  end
77
79
 
78
- def readMessage
79
- @server.readMessage
80
+ def read_message
81
+ @server.read_message
80
82
  end
81
83
 
82
84
  def inject(source)
83
85
  Thread.new do
84
- @server.sendCommand(Command::InjectionInject, source)
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 = @lastInjected[file]
110
+ time = @last_injected[file]
93
111
  if time == nil || now > time
94
- @lastInjected[file] = now
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 setProject(projectFile)
108
- @server.sendCommand(Command::InjectionProject, projectFile)
109
- # /Users/shantj/Documents/workspace/third/injection
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 listenCommand
133
+ def listen_command
118
134
  Thread.new do
119
- while (command = readInt) != Command::InjectionEOF
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 = readMessage
141
+ dylib = read_message
126
142
  signer = Signer.new
127
143
  ret = signer.codeSignDylib(dylib)
128
- @server.sendCommand(Command::InjectionSigned, ret ? '1' : '0')
144
+ @server.send_command(Command::InjectionSigned, ret ? '1' : '0')
129
145
  when Command::InjectionError
130
- puts "Injection error: #{readMessage}"
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/env ruby -w
1
+ #!/usr/bin/ruby -w
2
2
  # -*- coding : utf-8 -*-
3
+ #
3
4
  require_relative 'hotreload'
4
5
 
5
- @server = HotReload.new
6
- @server.startServer("/Users/shantj/Documents/workspace/third/HotReloadClient/Example/Pods")
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
- sockaddr = Socket.sockaddr_in(Config::PORT, Config::HOST)
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 readInt
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 readMessage
45
+ def read_message
45
46
  raise "client not connect #{@host}:#{@port}, please check" if @client == nil
46
47
 
47
48
  begin
48
- len = readInt
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 sendMessage(msg)
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 sendCommand(command, msg)
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
- sendMessage(msg)
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,10 @@
1
+ module Tool
2
+ def self.escaping(target_str, chars, temp)
3
+ if !temp
4
+ temp = "\\$0"
5
+ end
6
+
7
+ target_str.gsub! "[#{chars}]", temp
8
+ target_str
9
+ end
10
+ end
@@ -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,10 @@
1
+ module XcodeResultBundleProcessor
2
+
3
+ class KeywordStruct < Struct
4
+ def initialize(*args, **kwargs)
5
+ super()
6
+ param_hash = kwargs.any? ? kwargs : Hash[members.zip(args)]
7
+ param_hash.each { |k, v| self[k] = v }
8
+ end
9
+ end
10
+ 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.1.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-05-23 00:00:00.000000000 Z
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
- rubyforge_project:
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