class HotReload
Public Class Methods
new()
click to toggle source
# File lib/hotreload.rb, line 18 def initialize @project_injected = Hash.new @pending = [] @project_path = "" @project_file = "" @server = nil @last_injected = "" @hot_reload_id = 0 @hot_reload_tmp_path = "" @compile_class = Hash.new @xcode_dev_path = "/Applications/Xcode.app/Contents/Developer" @task_queue = Queue.new end
test(path)
click to toggle source
# File lib/hotreload.rb, line 14 def self.test(path) puts "success, path is #{path}" end
Public Instance Methods
build(file)
click to toggle source
rebuild file
# File lib/hotreload.rb, line 170 def build(file) # project_model : project_file , build_Log project_model = get_project_model(file) project_file = project_model["project_file"] build_log_dir = project_model["build_log"] puts "project_file=#{project_file}, build_log=#{build_log_dir}" @hot_reload_id += 1 file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}" log_file = "#{file_tmp}.log" compile_model = @compile_class[file] if !compile_model compile_model = get_compile_model(build_log_dir, file, file_tmp) if !compile_model puts "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.)" return end end puts "Compiling #{file}" project_dir_temp = Tool::escaping(@project_path, "$", "\\$0") compile_command = compile_model["compile_command"] command = "(cd #{project_dir_temp}) && #{compile_command} -o #{file_tmp}.o >#{log_file} 2>&1" ret = system(command) if !ret @compile_class.delete(file) puts "Re-compilation failed (#{file_tmp}.sh)\n#{File.read(log_file)})" return end # save in memory @compile_class[file] = compile_model # link result object file to create dylib puts "Creating dylib" regex = "\\s*(\\S+?\\.xctoolchain)" tool_chain = nil regex_result = compile_command.match(regex) if regex_result tool_chain = regex_result.captures[0] end if !tool_chain tool_chain = "#{@xcode_dev_path}/Toolchains/XcodeDefault.xctoolchain" end os_specific = "" cpu_arch = "arm64" is_real_device = true if compile_command.include?("iPhoneSimulator.platform") 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" cpu_arch = "x86_64" is_real_device = false elsif compile_command.include?("iPhoneOS.platform") 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" cpu_arch = "arm64" end 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" ret = system(link_command) if !ret puts "Link failed, check #{@hot_reload_tmp_path}/command.sh \n #{log_file}" return end puts "codesign dylib" codesigner = Signer.new() codesigner.pre_codesign(build_log_dir, is_real_device) ret = codesigner.codeSignDylib("#{file_tmp}.dylib") if !ret puts "codesign failed" return else puts "codesign success \n #{file_tmp}.dylib" return "#{file_tmp}.dylib" end end
build_log_path(project_file, derive_data_path)
click to toggle source
get xcode build log
# File lib/hotreload.rb, line 264 def build_log_path(project_file, derive_data_path) # file = "/path/to/xyz.mp4" ext = File.extname(project_file) # => ".mp4" name = File.basename(project_file, ext) # => "xyz" prefix = "#{name}"+"-" build_log_array = [] Dir.entries(derive_data_path).each do |sub| if sub.length <= prefix.length next end if sub.start_with?(prefix) buildLogDir = derive_data_path + "/" + sub + "/Logs/Build" if Dir.exist?(buildLogDir) build_log_array.push(buildLogDir) end end end log_sort = build_log_array.sort!{ |x,y| File.mtime(y) <=> File.mtime(x) } log_sort[0] end
changeFiles(changeFiles)
click to toggle source
# File lib/hotreload.rb, line 119 def changeFiles(changeFiles) now = Time.now.to_i changeFiles.each do |file| unless @pending.include?(file) time = @last_injected[file] if time == nil || now > time @last_injected[file] = now @pending.push(file) end end end @pending.each do |file| inject(file) end @pending.clear end
get_compile_model(build_log_dir, file, tmp_file)
click to toggle source
get compile command and changed file
# File lib/hotreload.rb, line 289 def get_compile_model(build_log_dir, file, tmp_file) if !File.file?(file) return end # find build command CompileCommand.new().getCompileCommand(build_log_dir, file, tmp_file) compile_command_file = "#{tmp_file}.sh" if !File.exist?(compile_command_file) puts "compile command parse error" return end compile_command = File.read("#{tmp_file}.sh") compile_command = compile_command.split(" -o ")[0] + " " compile_model = Hash["compile_command" => compile_command, "file" => file] end
get_project_model(file)
click to toggle source
get project file and xcode build log path
# File lib/hotreload.rb, line 250 def get_project_model(file) if !file.start_with?('/') puts "file is not validate: #{file}" return end # default deriveData path derive_data_path = File.expand_path("~/Library/Developer/Xcode/DerivedData") build_log = build_log_path(@project_file, derive_data_path) project_model = Hash["project_file" => @project_file, "build_log" => build_log] end
get_symbol_file()
click to toggle source
get class symbols
# File lib/hotreload.rb, line 310 def get_symbol_file file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}" 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" ret = system(symbol_command) if !ret puts "Could not list class symbols" end class_file = "#{file_tmp}.classes" end
inject(source)
click to toggle source
# File lib/hotreload.rb, line 115 def inject(source) @task_queue << source end
read_int()
click to toggle source
# File lib/hotreload.rb, line 79 def read_int @server.read_int end
read_message()
click to toggle source
# File lib/hotreload.rb, line 83 def read_message @server.read_message end
runTask()
click to toggle source
# File lib/hotreload.rb, line 87 def runTask Thread.new do # rebuild while true do source = @task_queue.pop @server.send_command(Command::HRCommandBuilding, source) dylib = build(source) if !dylib puts "build failed" @server.send_command(Command::HRCommandBuildFailed, source) next end symbol_file = get_symbol_file @server.send_command(Command::HRCommandTransferFile, nil) puts "start transfer file" file_tmp = "#{@hot_reload_tmp_path}/hotreload#{@hot_reload_id}" @server.send_file(dylib) @server.send_file("#{file_tmp}.o") @server.send_file(symbol_file) puts "finish transfer file" end end end
set_project(project_file)
click to toggle source
# File lib/hotreload.rb, line 138 def set_project(project_file) @server.send_command(Command::HRCommandSetProject, project_file) @watcher = FileWatcher.new(@project_path) @watcher.startWatcher do |file| changeFiles(file) end end
startServer(projectpath)
click to toggle source
# File lib/hotreload.rb, line 33 def startServer(projectpath) @project_path = File.expand_path("..", projectpath) @server = Server.new @server.run # create dir @hot_reload_tmp_path = "#{@project_path}/HotReload-tmp" if !Dir.exist?(@hot_reload_tmp_path) Dir.mkdir(@hot_reload_tmp_path) end # tell client the project being watched @project_file = "#{@project_path}/*.xcworkspace" files_sorted_by_time = Dir[@project_file].sort!{ |x,y| File.mtime(y) <=> File.mtime(x) } @project_file = files_sorted_by_time[0] if @project_file == nil @project_file = "#{@project_path}/*.xcodeproj" files_sorted_by_time = Dir[@project_file].sort!{ |x,y| File.mtime(y) <=> File.mtime(x) } @project_file = files_sorted_by_time[0] end if read_message != Config::HOTRELOAD_KEY puts "not the validate client" return end # client spcific data for building # frameworkPath = readMessage # arch = readMessage @last_injected = @project_injected[@project_file] if @last_injected == nil @last_injected = Hash.new @project_injected[@project_file] = @last_injected end # listen_command runTask set_project(@project_file) @watcher = nil end