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 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