美文网首页Flutter
podhelper.rb源码解析

podhelper.rb源码解析

作者: 笑破天 | 来源:发表于2020-10-10 17:01 被阅读0次

    前言:iOS工程中引入flutter。我们第一步要做的事是,在podfile里面写入类似下面代码。

    flutter_app_path = '../my_flutter' #flutter工程目录
    load File.join(flutter_app_path, '.ios', 'Flutter', 'podhelper.rb') #导入podhelper.rb文件
    install_all_flutter_pods(flutter_app_path) #调用podhelper.rb文件的install_all_flutter_pods方法
    

    下面分析podhelper.rb都干了啥。
    总体结构:1个require、6个方法


    image.png

    Part1、require语法

    require 'json'
    

    require。类似于include和import,引入一个模块。模块(Module)是一种把方法、类和常量组合在一起的方式。提供了命名空间、实现了混入(mixin)
    Load和require都能加载。模块的状态会频繁地变化, 我们使用 load 进行加载,获取最新的状态。不变化的我们用require。

    Part2、6个方法

    1、install_all_flutter_pods

    def install_all_flutter_pods(flutter_application_path = nil)
      flutter_application_path ||= File.join('..', '..')
      install_flutter_engine_pod
      install_flutter_plugin_pods(flutter_application_path)
      install_flutter_application_pod(flutter_application_path)
    end
    

    1、def:定义一个方法
    2、||=:或等,为空则赋值,不为空自身
    3、File.join('..', '..'):用分割符(默认是/)把'..'和'..'组合起来,返回一个str。该例子返回一个'../..'

    2、install_flutter_engine_pod

    def install_flutter_engine_pod
      current_directory = File.expand_path('..', __FILE__)
      engine_dir = File.expand_path('engine', current_directory)
      if !File.exist?(engine_dir)
        # Copy the debug engine to have something to link against if the xcode backend script has not run yet.
        # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
        debug_framework_dir = File.join(flutter_root, 'bin', 'cache', 'artifacts', 'engine', 'ios')
        FileUtils.mkdir_p(engine_dir)
        FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir)
        FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir)
      end
    
      # Keep pod path relative so it can be checked into Podfile.lock.
      # Process will be run from project directory.
      engine_pathname = Pathname.new engine_dir
      # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
      project_directory_pathname = defined_in_file.dirname
      relative = engine_pathname.relative_path_from project_directory_pathname
    
      pod 'Flutter', :path => relative.to_s, :inhibit_warnings => true
    end
    
    

    1、__FILE__。这个变量代表文件自己的文件名,在podhelper.rb中是podhelper.rb。
    2、File.expand_path('..', 'b')。在b路径下追加路径'..',此例是b/..,也就是返回b的上级目录。
    3、flutter_root。方法
    4、FileUtils.mkdir_p。生成目录及其所有上级目录。例如,FileUtils.mkdir_p '/usr/local/lib/ruby'将生成下列所有目录(若没有的话)。 * /usr * /usr/local * /usr/local/bin * /usr/local/bin/ruby
    5、FileUtils.cp_r(a,b)。把文件a拷贝到b,如果目录b存在,就拷贝到b里面,如果b不存在,就把a拷贝到b,此时b是一个没有后缀名的文件
    6、Pathname.new engine_dir。等同Pathname.new(engine_dir),或Pathname(engine_dir)。通过str生成一个路径类Pathname的对象
    7、a.relative_path_from b。a相对于b的相对路径,也叫b->a的相对路径。Pathname('/Users/bigfly/Desktop').relative_path_from Pathname('/Users/bigfly/Desktop/my_flutter')是'..'
    8、.to_s。用来将对象以字符串的格式去描述、去输出。也就是说,所有对象都能使用字符串的描述格式。
    只要像鸭子,就能当成鸭子,这就是to_x。只有它真的是鸭子,才能当成鸭子,这就是to_xxx。例如调用obj的string方法时需要先强制转换为string类,用to_str。类似的有to_i和to_int、to_a和to_ary、to_h和to_hash

    总结:该方法是把flutter_root目录/Users/xxx/flutter/bin/cache/artifacts/engine/ios 下面的Flutter.framework和Flutter.podspec拷贝到flutter模块工程的engine目录/Users/bigfly/Desktop/my_flutter/.ios/Flutter/engine下面。然后把Flutter pod进来。如果engine存在就直接pod,如果不存在,就去flutter_root拷贝一份过来再pod。

    3、install_flutter_plugin_pods

    def install_flutter_plugin_pods(flutter_application_path)
      flutter_application_path ||= File.join('..', '..')
    
      # Keep pod path relative so it can be checked into Podfile.lock.
      # Process will be run from project directory.
      current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
      # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
      project_directory_pathname = defined_in_file.dirname
      relative = current_directory_pathname.relative_path_from project_directory_pathname
      pod 'FlutterPluginRegistrant', :path => File.join(relative, 'FlutterPluginRegistrant'), :inhibit_warnings => true
    
      symlinks_dir = File.join(relative, '.symlinks')
      FileUtils.mkdir_p(symlinks_dir)
    
      plugins_file = File.expand_path('.flutter-plugins-dependencies', flutter_application_path)
      plugin_pods = flutter_parse_dependencies_file_for_ios_plugin(plugins_file)
      plugin_pods.each do |plugin_hash|
        plugin_name = plugin_hash['name']
        plugin_path = plugin_hash['path']
        if (plugin_name && plugin_path)
          symlink = File.join(symlinks_dir, plugin_name)
          FileUtils.rm_f(symlink)
          File.symlink(plugin_path, symlink)
          pod plugin_name, :path => File.join(symlink, 'ios'), :inhibit_warnings => true
        end
      end
    end
    

    1、flutter_application_path:/Users/xxx/Desktop/my_flutter
    2、current_directory_pathname:/Users/xxx/Desktop/my_flutter/.ios/Flutter
    3、project_directory_pathname = /Users/xxx/Desktop/my_flutter/.ios
    4、symlinks_dir = /Users/xxx/Desktop/my_flutter/.ios/Flutter/.symlinks
    5、plugins_file = /Users/xxx/Desktop/my_flutter/.flutter-plugins-dependencies
    6、flutter_parse_dependencies_file_for_ios_plugin。方法。解析.flutter-plugins-dependencies这个json文件,返回一个key链plugins/ios的值
    7、FileUtils.rm_f(symlink):删除/Users/xxx/Desktop/my_flutter/.ios/Flutter/.symlinks/shared_preferences
    File.symlink(plugin_path, symlink):在symlink位置创建一个符号链接,指向plugin_path,此处plugin_path的值是"/Users/xxx/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.12/"

    总结:该方法是,先pod进来/Users/xxx/Desktop/my_flutter/.ios/Flutter/FlutterPluginRegistrant。然后解析.flutter-plugins-dependencies文件,得到key链plugins/ios的值,是个依赖库数组,遍历数组,对每个依赖库进行删除实体文件、创建符号链接、pod进来操作。

    4、install_flutter_application_pod

    def install_flutter_application_pod(flutter_application_path)
      current_directory_pathname = Pathname.new File.expand_path('..', __FILE__)
      app_framework_dir = File.expand_path('App.framework', current_directory_pathname.to_path)
      app_framework_dylib = File.join(app_framework_dir, 'App')
      if !File.exist?(app_framework_dylib)
        # Fake an App.framework to have something to link against if the xcode backend script has not run yet.
        # CocoaPods will not embed the framework on pod install (before any build phases can run) if the dylib does not exist.
        # Create a dummy dylib.
        FileUtils.mkdir_p(app_framework_dir)
        `echo "static const int Moo = 88;" | xcrun clang -x c -dynamiclib -o "#{app_framework_dylib}" -`
      end
    
      # Keep pod and script phase paths relative so they can be checked into source control.
      # Process will be run from project directory.
    
      # defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
      project_directory_pathname = defined_in_file.dirname
      relative = current_directory_pathname.relative_path_from project_directory_pathname
      pod 'my_flutter', :path => relative.to_s, :inhibit_warnings => true
    
      flutter_export_environment_path = File.join('${SRCROOT}', relative, 'flutter_export_environment.sh');
      script_phase :name => 'Run Flutter Build my_flutter Script',
        :script => "set -e\nset -u\nsource \"#{flutter_export_environment_path}\"\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build",
        :input_files => [
          File.join('${SRCROOT}', flutter_application_path, '.metadata'),
          File.join('${SRCROOT}', relative, 'App.framework', 'App'),
          File.join('${SRCROOT}', relative, 'engine', 'Flutter.framework', 'Flutter'),
          flutter_export_environment_path
        ],
        :execution_position => :before_compile
    end
    

    1、current_directory_pathname = /Users/xxx/Desktop/my_flutter/.ios/Flutter
    2、app_framework_dir = /Users/xxx/Desktop/my_flutter/.ios/Flutter/App.framework
    3、app_framework_dylib = /Users/xxx/Desktop/my_flutter/.ios/Flutter/App.framework/App
    4、echo "static const int Moo = 88;" | xcrun clang -x c -dynamiclib -o "#{app_framework_dylib}" -。shell命令
    5、${SRCROOT},ruby中全局变量用${}引用
    6、project_directory_pathname = /Users/xxx/Desktop/my_flutter/.ios
    7、flutter_export_environment_path =/Users/xxx/Desktop/my_flutter/.ios/Flutter/flutter_export_environment.sh
    8、script_phase 。给target添加编译前的shell脚本,脚本内容是

    set -e #之后的代码,一旦出错,停止执行并退出,避免后续一些脚本的危险操作
    set -u #遇到不存在的变量就会报错,并停止执行
    source "#{flutter_export_environment_path}"
    "$FLUTTER_ROOT"/packages/flutter_tools/bin/xcode_backend.sh build
    

    总结:该方法是,生成App.framework/app文件如果不存在的话,执行了一条shell命令(调用xcrun clang -o命令生成目标文件)。pod进来my_flutter。给target添加编译前的shell脚本,脚本主要执行了xcode_backend.sh build命令

    5、flutter_parse_dependencies_file_for_ios_plugin

    def flutter_parse_dependencies_file_for_ios_plugin(file)
      file_path = File.expand_path(file)
      return [] unless File.exists? file_path
    
      dependencies_file = File.read(file)
      dependencies_hash = JSON.parse(dependencies_file)
    
      # dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3
      return [] unless dependencies_hash.has_key?('plugins')
      return [] unless dependencies_hash['plugins'].has_key?('ios')
      dependencies_hash['plugins']['ios'] || []
    end
    

    解析.flutter-plugins-dependencies文件,并返回key链plugins/ios的值。

    6、flutter_root

    def flutter_root
      generated_xcode_build_settings_path = File.expand_path(File.join('..', '..', 'Flutter', 'Generated.xcconfig'), __FILE__)
      unless File.exist?(generated_xcode_build_settings_path)
        raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
      end
    
      File.foreach(generated_xcode_build_settings_path) do |line|
        matches = line.match(/FLUTTER_ROOT\=(.*)/)
        return matches[1].strip if matches
      end
      # This should never happen...
      raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
    end
    

    1、File.foreach读取文件的每一行并执行一个block。File.readlines读取文件的每一行到一个数组里面。
    2、line.match匹配结果是个数组,包括两部分:0完整匹配部分、1匹配到的内容

    总结:该方法,解析Flutter/Generated.xcconfig文件,遍历每一行,找到FLUTTER_ROOT的值并返回。

    总结:该文件主要有三个方法:install_flutter_engine_pod、install_flutter_plugin_pods、install_flutter_application_pod。分别作用是:不存在就新生成并pod进来engine/Flutter.framework/flutter、pod进来FlutterPluginRegistrant+plugins、不存在就新生成app.framework/app并pod进来my_flutter。

    思考:

    1、更改flutter工程的dart代码,重新运行iOS工程(不是Runner),为什么代码能生效?
    pod install的时候,执行了podhelper.rb,执行了install_flutter_application_pod方法,给target添加了编译前shell脚本。所以pod install 过后,每次运行iOS工程都会执行xcode_backend.sh build命令。

    相关文章

      网友评论

        本文标题:podhelper.rb源码解析

        本文链接:https://www.haomeiwen.com/subject/hoeuuktx.html