class Fastlane::Helper::LinkMap::Parser

Attributes

library_map[RW]
object_map[RW]
section_map[RW]
segment_map[RW]

Public Class Methods

new(options) click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 14
def initialize(options)
  @file_path   = options[:file_path]
  @all_objects = options[:all_objects] || false
  @all_symbols = options[:all_symbols] || false

  unless @file_path
    raise "❌ [Parser] file_path not pass"
  end

  unless File.exist?(@file_path)
    raise "❌ [Parser] file at #{@file_path} not exist"
  end

  @object_map  = {}
  @library_map = {}
  @section_map = {}
  @segment_map = {}

  parse
end

Public Instance Methods

generate_hash() click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 186
def generate_hash
  return @hash_result if @hash_result

  # sort object_map[i].ObjectFile.symbols
  @object_map.each do |_, object_file|
    next unless object_file.symbols

    object_file.symbols.sort! do |sym1, sym2|
      sym2.size <=> sym1.size
    end
  end

  # 计算 linkmap.txt 所有的 symbol 总大小
  total_size = @library_map.values.map(&:size).inject(:+)
  total_dead_size = @library_map.values.map(&:dead_symbol_size).inject(:+)

  # sort library_map[i]
  sorted_librarys = @library_map.values.sort do |a, b|
    b.size <=> a.size
  end

  # fixed library_map[i].object_files[i]
  fixed_librarys = sorted_librarys.map { |lib|
    fixed_library = lib.to_hash(@all_objects)

    if @all_objects
      fixed_library[:object_files] = lib.object_files.map { |object_file_index|
        # 修正 object file [1,2,3,...] ==> ["TXVodPlayerStatsCollection.o", "TXVodDownloadManager.o", "TXUGCVideoRecorder.o", ...]
        object_file = object_map[object_file_index]

        # fixed library_map[i].object_files[i].symbols 是否打印每一个 object file 下面 all symbol
        if object_file
          object_file.to_hash(@all_symbols)
        else
          nil
        end
      }.compact
    end

    fixed_library
  }

  @hash_result = {
    count: fixed_librarys.count,
    size: total_size,
    format_size: Fastlane::Helper::LinkMap::FileHelper.format_size(total_size),
    dead_size: total_dead_size,
    format_dead_size: Fastlane::Helper::LinkMap::FileHelper.format_size(total_dead_size),
    librarys: fixed_librarys
  }
  @hash_result
end
generate_json() click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 179
def generate_json
  return @json_result if @json_result

  @json_result = JSON.generate(generate_hash)
  @json_result
end
generate_merge_by_pod_hash() click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 245
def generate_merge_by_pod_hash
  return @merge_hash_result if @merge_hash_result

  @merge_hash_result = {
    count: generate_hash[:count],
    size: generate_hash[:size],
    format_size: generate_hash[:format_size],
    dead_size: generate_hash[:dead_size],
    format_dead_size: generate_hash[:format_dead_size]
  }

  # 合并 subspec 下的 library
  # AlibcSDK.podspec
  # ------------------------------------------------------------------
  # $ cd /path/to/App/Pods/AlibcSDK/AlibcSDK/Frameworks
  # $ tree -d -L 3
  # .
  # ├── AliAuthSDK
  # │   ├── AlibabaAuthExt.framework
  # │   └── AlibabaAuthSDK.framework
  # ├── AliLinkPartnerSDK
  # │   └── AlibcLinkPartnerSDK.framework
  # ├── AlibcTradeSDK
  # │   ├── AlibcTradeBiz.framework
  # │   └── AlibcTradeSDK.framework
  # ├── BCUserTrack
  # │   └── UTMini.framework
  # ├── UTDID
  # │   └── UTDID.framework
  # ├── mtopSDK
  # │   ├── MtopSDK.framework
  # │   ├── mtopcoreopen.framework
  # │   └── mtopext.framework
  # └── securityGuard
  #     ├── SGAVMP.framework
  #     ├── SGMain.framework
  #     ├── SGMiddleTier.framework
  #     ├── SGSecurityBody.framework
  #     └── SecurityGuardSDK.framework
  #
  pod_hash = Hash.new
  generate_hash[:librarys].each_with_object(pod_hash) do |lib, hash|
    apod_librarys = hash[lib[:podspec_name]]
    apod_librarys ||= Array.new
    apod_librarys << lib
    hash[lib[:podspec_name]] = apod_librarys
  end

  @merge_hash_result[:pods] = pod_hash
  @merge_hash_result
end
generate_merge_by_pod_json() click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 239
def generate_merge_by_pod_json
  return @merge_json_result if @merge_json_result
  @merge_json_result = JSON.generate(generate_merge_by_pod_hash)
  @merge_json_result
end
parse() click to toggle source

读取并解析 Linkmap.txt 文件的【每一行】内容

# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 38
def parse
  File.foreach(@file_path).with_index do |line, num|
    # begin
      unless line.valid_encoding?
        line = line.encode("UTF-16", :invalid => :replace, :replace => "?").encode('UTF-8')
      end

      if line.start_with? "#"
        if line.start_with? "# Object files:"
          @subparser = :parse_object_files
        elsif line.start_with? "# Sections:"
          @subparser = :parse_sections
        elsif line.start_with? "# Symbols:"
          @subparser = :parse_symbols
        elsif line.start_with? '# Dead Stripped Symbols:'
          @subparser = :parse_dead_stripped_symbols
        end
      else
        send(@subparser, line)
      end
    # rescue => e
    #   UI.error "Exception on LinkMap file line #{num}:"
    #   # UI.message line
    # end
  end
  # puts "There are #{@section_map.values.map{|value| value[:residual_size]}.inject(:+)} Byte in some section can not be analyze"
end
parse_dead_stripped_symbols(line) click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 163
def parse_dead_stripped_symbols(line)
  stripped_symbol = DeadStrippedSymbol.new(line)
  return if stripped_symbol.invalid #=> line parse failed

  object_file = @object_map[stripped_symbol.file]
  return unless object_file #=> line can not found object file

  # 累加 一个 ObjectFile 总大小 (包含 N个 Dead Stripped Symbol)
  object_file.dead_symbol_size += stripped_symbol.size

  # 累加 一个 Library 总大小 (包含 N个 ObjectFile)
  library = @library_map[object_file.library]
  return unless library
  library.dead_symbol_size += stripped_symbol.size
end
parse_object_files(line) click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 66
def parse_object_files(line)
  ObjectFile.new(line) do |of, type|
    # 保存解析完成的 ObjectFile
    @object_map[of.index] = of

    # xx.o 没有 Library 的情况
    next if OBJECT_FILE_TYPE_SYSTEM == type #=> 1) system 类型的 xx.o 不创建 Library
    next unless of.library                  #=> 2) xx.o 没有归属的 library

    # 创建/获取 xx.o 归属到的 library
    library = @library_map[of.library]
    library ||= Library.new({
      name: of.library,
      size: of.size,
      object_files: Array.new,
      dead_symbol_size: of.dead_symbol_size
    })

    # 只有【用户】类型的【xx.a】和【xx.framework】, 才可能是 CocoaPods 方式集成, 也才会有 podspec name
    # podspec、subspec
    ## - 1) 没有 subspec
    # /path/to/Pods/BaiduMobAdSDK/BaiduMobAdSDK/BaiduMobAdSDK.framework ==> podspec name: BaiduMobAdSDK
    ## - 2) 有 subspec
    # /path/to/Pods/AlibcSDK/AlibcSDK/Frameworks/UTDID/UTDID.framework  ==> podspec name: AlibcSDK, subspec name: UTDID
    # /path/to/Pods/AlibcSDK/AlibcSDK/Frameworks/AliAuthSDK/AlibabaAuthExt.framework ==> podspec name: AlibcSDK, subspec name: AliAuthSDK
    # /path/to/Pods/AlibcSDK/AlibcSDK/Frameworks/AliAuthSDK/AlibabaAuthSDK.framework ==> podspec name: AlibcSDK, subspec name: AliAuthSDK
    #
    # 结论
    # - 1) /path/to/pods/<Podspec#name>/.../xx.framework 或 xx.a
    # - 2) /path/to/pods/<Podspec#name>/.../<Subspec#name>/xx.framework 或 xx.a ==> <Subspec#name> 不好确定
    #
    if OBJECT_FILE_TYPE_USER_LIBRARY == type
      if line.include?('/Pods/')
        # [ 23] /path/to/App/Pods/AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o)
        divstr = line.split('/Pods/').last      #=> AFNetworking/AFNetworking.framework/AFNetworking(AFAutoPurgingImageCache.o)
        podspec_name = divstr.split('/').first  #=> AFNetworking
        library.podspec_name = podspec_name
      else
        library.podspec_name = nil
      end
    end

    # library【追加】位于 `# Object Files` 后面【xx.o 目标文件】的 下标值 [ n]
    library.object_files << of.index

    # 保存/更新 library
    @library_map[of.library] = library
  end
end
parse_sections(line) click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 116
def parse_sections(line)
  section                   = Section.new(line)
  @section_map[section.key] = section
end
parse_symbols(line) click to toggle source
# File lib/fastlane/plugin/analyze_ios_linkmap/helper/analyze_ios_linkmap_parser.rb, line 121
def parse_symbols(line)
  symbol = Fastlane::Helper::LinkMap::Symbol.new(line)
  return if symbol.invalid #=> line parse failed

  object_file = @object_map[symbol.file]
  return unless object_file #=> line can not found object file

  # 累加1: 一个 ObjectFile 总大小 (包含 N个 Symbol)
  object_file.add_symbol(symbol)

  # 累加2: 一个 Library 总大小 (包含 N个 ObjectFile)
  library      =  @library_map[object_file.library]
  library.size += symbol.size if library

  # 累加3: 一个 Segment 总大小 (包含 N个 Section)
  ## 找到 当前被解析 symbol 所属的 section
  section = @section_map.detect do |_, sec|
    if sec
      (sec.start_addr...sec.end_addr).include?(symbol.address)
    else
      false
    end
  end #=> [:"__TEXT:__text", #<FastlaneCore::Helper::LinkMap::Section:0x00007fd8e3787eb8 ...>]
  ## 再把 当前被解析 symbol 符号总大小, 累加到
  if section
    key          = section[0] #=> :"__TEXT:__text"
    value        = section[1] #=> #<FastlaneCore::Helper::LinkMap::Section:0x00007fd8e3787eb8 ...>

    segment_name = value.to_segment           #=> 解析出 section 所属的 segment name
    segment      = @segment_map[segment_name] #=> 尝试从 segment map 中, 查找是否有 缓存的 segment 对象

    unless segment
      segment = Segment.new({ name: segment_name, size: symbol.size, residual_size: symbol.size })
    else
      segment.size          += symbol.size
      segment.residual_size += symbol.size
    end

    @segment_map[segment_name] = segment
  end
end