configure 查找依赖库_一步步教你二进制依赖库看源码,不依赖运行!

本文介绍了在二进制依赖库环境下,如何在编写代码时查看源码,不依赖lldb运行环境。通过分析Podfile,下载源码,创建壳工程并集成到主工程,实现源码查看功能。详细步骤包括确定查看的二进制库,查找二进制引入的库,下载源码,移动源码,创建壳工程,最后整合到工程中。
摘要由CSDN通过智能技术生成

之前美团出了一篇讲在二进制依赖库的情况下,调试的时候查看依赖库源码的办法,见美团 iOS 工程 zsource 命令背后的那些事儿

这篇文章发出来之后,很快有人就跟进了更加简便的,见lldb 入坑指北(2)- 15行代码搞定二进制与源码映射

二者都是很好的文章,解决了在二进制依赖库的环境下,运行环境查看源码的问题。当时看到这些文章的时候,头脑一热:能否不依赖于lldb运行环境,在写代码的时候就可以跳转到源码去看呢?经过一番捣鼓,有了结果 iOS组件化过程中的源码查看。这篇文章只是个demo,提供了思路,但是没有一个工程化的代码。今天就来补充一下一些工程代码。当然,这里的代码是依赖于我司现在用的二进制解决方案的,具体到不同的二进制解决方案的时候,可以参照这个代码修改一下。

思路再重复一遍:

  • 1、二进制文件里面其实包含了编译时候的源码路径的,只要Xcode能找到这个路径的代码,就可以跳转过去。
  • 2、我们需要根据二进制文件找到打包它的时候,源码的存放位置。并且下载二进制库对应的源码版本,放到对应的位置。
  • 3、将这些源码工程用一个拖进一个新的壳工程里面。然后将这个壳工程集成进我们主工程的workspace里面。

思路简单,但是搞成工程事情就多了。我说下我的选型思路:

第一,我们需要在podfile里面找到我们使用了二进制依赖的库,podfile是ruby写的。

第二,我们需要下载源码,这部分自己写程序是没问题的,但是cocoapods不是有下载的功能?

第三,我们需要搞个壳工程,并集合进去workspace里面。这个是不是和Pods.xcodeproj一样的么?那cocoapods就有这个能力。

综上,我们可以用ruby写,借助cocoapods的代码来帮助我们实现工程化。既然要用cocoapods,就需要先看懂一些源码,调试看最快了,请参考我之前写的Ruby调试

先放一段结果视频,视频上的主工程是cocoTest,然后它的podfile里面引用了一个二进制的依赖库StaticLiBit.(故意写少了个b5a376529f1c8b250ac59e0ab08ce0a15.png),二进制库里面有个Animal类。注意视频开始的时候,点击Animal的定义,跳转到了二进制库的头文件,但是再次跳转到gogoPower方法定义的时候,它切换到了我们的源码工程里面去了。这样就实现了在写代码的时候,查看二进制库源码的功能。

下面让我们一步一步,手把手教你怎么搞。我刚接触ruby,代码写得有点难看,就别喷这个点了。。

1 定义哪些二进制库要查看二进制源码的

因为我们可能引入了一大堆的二进制库,而里面想看的其实不多,那么可以搞个白名单,只有在白名单里面的库,我们才去下载源码。所以我定义了一个sourcePodList.rb,里面每一行就写一个库的名字。然后用ruby将这个文件读入,这样就有了白名单数组

def Pod.readSourceFile(sourceFilePath = nil) #读入需要引入源码的二进制库名单
if sourceFilePath == nil || !File.exist?(sourceFilePath)
puts "\033[33mWarning sourceFile not found\033[0m\n"
return []
end
sourcePods = []
File.open(sourceFilePath, "r") do |file|
file.each_line do |line|
sourcePods << line.strip
# puts line
end
end
sourcePods
end

2 在podFile里面查找哪些库是使用了二进制引入的

假设我的二进制引入的命令是这样的。后面新增的:bin=>true就是意味着打开了引入二进制库。如果不存在这个参数,则认为走源码引入。

pod_ek 'StaticLiBit', :git=>"http://ek.com/StaticLibBit.git", :commit => 'eaceb85197c79947cb16af21d9d3ec16c3397abf', :bin=>true

你需要知道ruby每一行都可以看成一个函数调用。我们可以定义一个pod_ek的方法,然后就可以执行上面的命令。这样就可以读取到这行里面的所有的参数。并且将一些不需要用到的参数去除。读取完podFile里面的二进制库之后,我们再和第一步里面定义的白名单进行匹配,得出我们最终需要下载源码的依赖库名单。这里我是放到了全局变量@@dependencies里。

    def Pod.pod_ek(name = nil, *requirements)
params = requirements[0].clone
if params[:bin] == true
params.delete(:bin)
params.delete(:subspecs)
params.delete(:testspecs)
params.delete(:appspecs)
pod = { "name" => name, "params" => params }
# puts pod
@@dependencies << pod
end
end

def Pod.readUNQPodFile(file_path = nil, sourcePods = [])
if file_path == nil || !File.exist?(file_path)
puts "\033[33mWarning UNQPod.rb file not found\033[0m\n"
return
end
File.open(file_path, "r") do |file|
file.each_line do |line|
if line.lstrip.start_with? "pod_ek"
eval(line, nil, file_path) # 你需要知道ruby每一行都可以看成一个函数调用
# puts line
end
end
end
dependencies = []
@@dependencies.each do |dependency|
# puts dependency["name"]
if sourcePods.include?(dependency["name"])
puts "found bin lib:#{dependency["name"]}"
dependencies << dependency
end
end
@@dependencies = dependencies
end

3、下载我们需要的源码库

这一步就比较特殊了,每家厂的二进制依赖方案不同,怎么从podfile里面引入的二进制依赖库转换到源码库的下载的pod命令,这个需要你们自己解决了。(因为我的只需要将:bin=>true删除就行5a376529f1c8b250ac59e0ab08ce0a15.png),所以我只放出下载代码。dependency["params"]这里面存放最基础的pod命令里面需要的参数就行了。cocoapods会解析后进行下载,毕竟是个中心化的依赖管理器。。。

    def Pod.downloadSource(dependencies = [])
download_results = []
dependencies.each do |dependency|
download_request = Downloader::Request.new(:name => dependency["name"],
:params => dependency["params"],
)
begin
download_result = Downloader.download(download_request, nil, :can_cache => true) # 第二个参数传递一个文件夹路径的话,可以将库拷贝过去
podPath = download_request.slug({})
rr = @@cache_root + 'Pods' + podPath
download_results << {"name" => dependency["name"], "podPath" => rr}
# puts rr
rescue Pod::DSLError => e
raise Informative, "Failed to load '#{name}' podspec: #{e.message}"
rescue => e
raise Informative, "Failed to download '#{name}': #{e.message}"
end
end
download_results
end

4、移动下载好的源码到二进制库编译的路径

首先,我们需要从二进制依赖库.a里面找到编译的路径,见下面代码

   def Pod.findLib(libName = nil)
   #这个方法就是根据库的名字,在Pods里面找到这个二进制库
      if libName == nil
          puts "\033[33mWarning libName is nil\033[0m\n"
          return ""
      end
       # @@sandbox_root = Config.instance.sandbox_root, /工程目录/Pods
      libFolderPath = @@sandbox_root + libName
      libPath = ....  #根据自己的二进制方案,找到这个二进制.a文件
      libPath
  end
   # 去找到二进制里面的目录
  def Pod.findLibSourcePath(libName = nil)
      if libName == nil
          puts "\033[33mWarning libName is nil\033[0m\n"
          return ""
      end
      libPath = Pod.findLib(libName)
      if File.exist?(libPath)
           # 这里需要这个二进制库是在库名字的目录下build出来的,否则查找不正确
          path = `str=\`dwarfdump #{libPath} | grep 'DW_AT_decl_file' | grep #{libName} | head -n 1\`;str=${str#*\\"};echo ${str%%#{libName}*}#{libName}`
          return path.strip # 这里居然有个换行符!!!      else
          puts "\033[33mWarning lib:#{libPath} is not found\033[0m\n"
          return nil
      end
  end

找到编译路径之后,我们需要将第三步下载的源码移动到对应的地方

    def Pod.copyLibsToDest(download_results)
#移动需要的pod到指定位置
download_results.each do |result|
# 去找到二进制里面的目录
libDestPath = Pod.findLibSourcePath(result["name"])
if libDestPath == nil
return
end
puts "find #{result["name"]} DestPath:#{libDestPath}"
if !libDestPath.start_with?("/") && !libDestPath.start_with?("./")
libDestPath = Dir.pwd + "/" + libDestPath #有些很奇怪的以当前文件夹来做路径的,则需要拼接上当前路径。否则copy库的时候会有问题。
end
# 拷贝cache里面的库到指定位置
result["libDestPath"] = libDestPath
puts "copy lib to :#{libDestPath}"
if File.exist?(libDestPath)
FileUtils.chmod_R "u=wrx,go=rx", libDestPath # 加回权限,否则删除不了
FileUtils.rm_rf libDestPath
elsif
FileUtils.mkdir_p(File.dirname(libDestPath))
end
FileUtils.cp_r(result["podPath"], libDestPath)
end
end

5、用移动好的源码,创建一个壳工程。并添加到主工程

主要难点是在遍历添加文件上,这个弄了我好久才搞定。。

    $codeExt = [".m", ".h", ".c", ".cpp", ".mm", ".pch"]
$sourceExt = [".m", ".c", ".cpp", ".mm"]
def Pod.addLib(lib_path, group)
if File.directory? lib_path
Dir.foreach(lib_path) do |file|
Pod.addLibFiles(lib_path+"/"+file, group)
end
else
end
end

def Pod.addLibFiles(file_path, group, target)
# puts "ff:#{file_path}"
if File.directory? file_path
# puts "AddGroup:#{file_path}"
g=group.new_group(File.basename(file_path))
Dir.foreach(file_path) do |file|
if file !="." and file !=".."
addLibFiles(file_path+"/"+file, g, target)
end
end
if g.empty?
group.children.delete_at(group.children.index(g))
else
g.sort
end

else
if $codeExt.include?(File.extname(file_path))
# basename=File.basename(file_path)
# puts basename
# puts "AddFile:#{File.basename(file_path)}"
file_ref = group.new_reference(file_path)
if $sourceExt.include?(File.extname(file_path))
target.add_file_references([file_ref])
end
target.add_resources([file_ref])
FileUtils.chmod "u=rx,go=rx", file_path
end
end
end

# 创建工程文件
def Pod.createXcodeProj(targetName, download_results, workspacePath)
projName = "SourcePod"
projPath = "sourcePod/#{projName}.xcodeproj"
puts "create #{projPath}"
proj = Xcodeproj::Project.new(projPath)
download_results.each do |result|
target = proj.new_target(:static_library, result["name"], :ios, '9.0')
Pod.addLibFiles(result["libDestPath"], proj.main_group, target)
end
proj.save()
# 添加到之前的workspace
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspacePath)
root = workspace.document.root
found = false
root.children.each do |child|
if child.is_a?(REXML::Element)
location = child.attributes['location']
if location.include?(projName)
# root.delete_element(child)
found = true
break
end
end
end
if !found
workspace << projPath
workspace.save_as(workspacePath)
end
end

这里有个坑啊。。就是workspace里面是可以添加重名的project的,如果上面的代码一直运行,就会在一个workspace里面添加了很多很多个壳工程。。所以我们需要进行删除。

    def Pod.deleteSourceProj(workspacePath)
puts "try remove sourcePod.proj"
projName = "SourcePod"
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspacePath)
root = workspace.document.root
found = false
root.children.each do |child|
if child.is_a?(REXML::Element)
location = child.attributes['location']
if location.include?(projName)
root.delete_element(child)
found = true
puts "found and delete SourceProj"
break
end
end
end
if found
workspace.save_as(workspacePath)
end
end

6、最后,搞个方法将这些串起来

我们的方法最好提供开关功能,因为经测试某些xcode版本会出现编译不过的问题。或者只提供拷贝代码到编译目录的功能。这样的话,其实不需要依赖之前的lldb的文章就可以在运行环境看源码了d8e2291a3b74ed381749c704b8ec7cc4.png

# SourceOpen是总开关;
# OnlyInRuntime为true的时候,不增加SourcePod工程,只拷贝代码到目录,运行时可以进入源码
# target 主工程的target名字
# workspacePath 主工程workspace路径
def Pod.sourceLook(sourceOpen, onlyInRuntime, targetName, workspacePath)
# 创建工程文件, 并添加
if !sourceOpen
Pod.deleteSourceProj(workspacePath)
else
#下载需要的pod
sourcePods = Pod.readSourceFile(@@sourceFile)
if sourcePods.empty?
Pod.deleteSourceProj(workspacePath)
return
end
Pod.readUNQPodFile(@@podrb_path, sourcePods)
download_results = Pod.downloadSource(@@dependencies) # [{"name"=>"PINCache", "podPath"=>"External/PINCache/26171f2f43ff1f67d79600e4dfd4d53e"}]
#移动需要的pod到指定位置
Pod.copyLibsToDest(download_results)
if !onlyInRuntime
Pod.createXcodeProj(targetName, download_results, workspacePath)
else
Pod.deleteSourceProj(workspacePath)
end
end
end

7、接入工程

我们最后加入一个类似的main的入口函数

    def Pod.sourceLookEvnMain
puts " ***** Source Look Begin *******"
isJenkis = ENV.fetch('IS_JENKINS', '0')
puts "isJenkis=#{isJenkis}"
if isJenkis == '0' # jenkin不需要这个功能
sourceOpen = false
onlyInRuntime = true
sourcePodConfigPath = "sourcePodConfig.rb"
if File.exist?(sourcePodConfigPath)
sourcePodConfig = eval(File.read(sourcePodConfigPath))
sourceOpen = sourcePodConfig['sourceOpen'] if sourcePodConfig.key?('sourceOpen')
onlyInRuntime = sourcePodConfig['onlyInRuntime'] if sourcePodConfig.key?('onlyInRuntime')
else
File.open(sourcePodConfigPath, "w") do |aFile|
aFile.puts("# SourceOpen是总开关; OnlyInRuntime为true的时候,不增加SourcePod工程,只拷贝代码到目录,运行时可以进入源码")
aFile.puts("{")
aFile.puts("'sourceOpen' => #{sourceOpen},")
aFile.puts("'onlyInRuntime' => #{onlyInRuntime},")
aFile.puts("}")
end
end
puts "sourceOpen=#{sourceOpen}"
puts "onlyInRuntime=#{onlyInRuntime}"
Pod.sourceLook(sourceOpen, onlyInRuntime, @@targetName, @@workspacePath)
end
puts " ***** Source Look End *******"
end

# 程序开始
Pod.sourceLookEvnMain

最后在podFile里面加上这句,然后就可以在每次pod install的时候,自动下载并集成需要看源码的库了。d8e2291a3b74ed381749c704b8ec7cc4.png

post_install do |installer|
# 启用二进制看源码脚本
instance_eval File.read("/scripts/sourcePod.rb")
end

总的源码地址 https://github.com/Ekulelu/sourcePod 。

欢迎各位大佬点赞加关注。

393ebb21069cb6c684f08d376c617850.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值