微店的Flutter混合开发组件化与工程化架构

1. podhelper.rb

因Podfile是通过ruby语言写的,所以该脚本也是ruby脚本,该脚本在pod install/update时主要做了三件事:

  1. Pod本地依赖Flutter引擎(Flutter.framework)与Flutter插件注册表(FlutterPluginRegistrant)
  2. Pod本地源码依赖.flutter-plugins文件中包含的Flutter工程路径下的ios工程
  3. 在pod install执行完后post_install中,获取当前target工程对象,导入Generated.xcconfig配置,这些配置都为环境变量配置,主要为构建阶段xcode_backend.sh脚本执行做准备

上述事情即可保证Flutter工程以及传递依赖的都通过pod本地依赖进Native工程了,接下来就是构建了

2. xcode_backend.sh

该Shell脚本位于Flutter SDK中,该脚本主要就做了两件事:

  1. 调用flutter命令编译构建出产物(App.framework、flutter_assets)
  2. 把产物(*.framework、flutter_assets)拷贝到对应XCode构建产物中,对应产物目录为:$HOME/Library/Developer/Xcode/DerivedData/${AppName}

上述两个静态库*.framework是拷贝到${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app/Frameworks"目录下

flutter_assets拷贝到${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app"目录下

在XCode工程中,对应的是在${AppName}/Products/${AppName}.app

五、Flutter与Native通信

Flutter与Native通信有三种方式,这里只简单介绍下:

  1. MethodChannel:方法调用
  2. EventChannel:事件监听
  3. BasicMessageChannel:消息传递

Flutter与Native通信都是双向通道,可以互相调用和消息传递

接下来是本文的重点内容,上述主要是普及下Flutter工程上比较重要的内容以及为下面要讲做准备,当然还有打包模式、构建流程等就不放这里了,后面可以单独开一篇讲

六、Flutter版本一致性与自动化管理

在团队多人协作开发模式下,Flutter SDK的版本一致性与自动化管理,这是个必须解决的问题,通过这个问题,我们回看Android中Gradle的版本管理模式:

Gradle的版本管理是通过包装器模式,每个Gradle项目都会对应一个Gradle构建版本,对应的Gradle版本在gradle-wrapper.properties配置文件中进行配置,如果执行构建时本地没有当前工程中对应的Gradle版本,则会自动下载所需的Gradle版本,而执行构建则是通过./gradlew包装器模式进行执行,这样本地配置的全局Gradle环境与工程环境即可隔离开,对应的项目始终保持同一个Gradle版本的构建

这种包装器模式的版本管理方式,可与每台机器中全局配置的环境保持隔离,在团队多人协作下,也可保持同一个项目工程保持同一个构建版本

所以,我们沿用Gradle版本管理思想,在每个Flutter工程(包含上述说的四种工程)的根目录加入三个文件:

wrapper/flutter-wrapper.properties

flutterw

flutterw.bat

加入后的项目结构则多了三个文件,如下:

上述flutter-wrapper.properties为当前工程Flutter SDK版本配置文件,内容为:

distributionUrl=https://github.com/flutter/flutter

flutterVersion=1.0.0

当然有需要可以再增加一些配置,目前这两个配置已经足够了,指定了Flutter的远程地址以及版本号,如果Clone Github上项目比较慢,也可以改为私有维护的镜像地址

flutterw为一个Shell脚本,内部对版本管理主要做的事情为:

  1. 读取配置的版本号,校验Flutter SDK版本,不存在则触发下载
  2. 更新Android中local.properties和IOS中Generated.xcconfig文件中Flutter SDK地址
  3. 最后把命令行传来的参数链接到Flutter SDK中的flutter进行执行

之后构建Flutter工程则用flutterw命令:

./flutterw build bundle

而不用本地全局配置的flutter命令,避免每个开发同学版本不一致问题,且这种方式对于新加入Flutter开发的同学来说,完全不需要自己手动下载Flutter SDK,只需执行一下flutterw任何命令,如./flutterw --version,即可自动触发对应Flutter SDK的下载与安装,实现优雅的自动化管理,这种方式对打包平台来说也为支持Flutter工程的打包提供基础

七、Flutter混合开发组件化架构

上述说的如果我们要利用Flutter来开发我们现有Native工程中的一个模块或功能,肯定得不能改变Native的工程结构以及不影响现有的开发流程,那么,以何种方式进行混合开发呢? 前面说到Flutter的四种工程模型,Flutter App我们可以直接忽略,因为这是一个开发全新的Flutter App工程,对于Flutter Module,官方提供的本地依赖便是使用Flutter Module依赖到Native App的,而对于Flutter工程来说,构建Flutter工程必须得有个main.dart主入口,恰好Flutter Module中也有主入口

于是,我们进行组件划分,通过Flutter Module作为所有通过Flutter实现的模块或功能的聚合入口,通过它进行Flutter层到Native层的双向关联。而Flutter开发代码写在哪里呢?当然可以直接写在Flutter Module中,这没问题,而如果后续开发了多个模块、组件,我们的Dart代码总不可能全部写在Flutter Module中lib/吧,如果在lib/目录下再建立子目录进行模块区分,这不失为一种最简单的方式,不过这会带来一些问题,所有模块共用一个远程Git地址,首先在组件开发隔离上完全耦合了,其次各个模块组件没有单独的版本号或Tag,且后续模块组件的增多,带来更多的测试回归成本

正确的组件化方式为一个组件有一个独立的远程Git地址管理,这样各个组件在发正式版时都有一个版本号和Tag,且在各个组件开发上完全隔离,后续组件的增多不影响其它组件,某个组件新增需求而不需回归其它组件,带来更低的测试成本

前面提到Flutter Plugin可以有对应Dart层代码与平台层的实现,所以可以这样设计,一个组件对应一个Flutter Plugin,一个Flutter Plugin为一个完整的Flutter工程,有独立的Git地址,而这些组件之间不能互相依赖,保持零耦合,所以这些组件都在业务层,可以叫做业务组件,这些业务组件之间的通信和公共服务可以再划分一层基础层,可以叫做基础组件,所有业务组件依赖基础层,而Flutter Module作为聚合层依赖于所有Flutter组件,这些Flutter工程之间的依赖正是通过Pub依赖进行管理的

所以,综合上述,整体的组件化架构可以设计为:

业务组件与基础组件的定位

对于上面的基础组件比如还可以进行更细粒度的划分,不过不建议划分太多,对于与Native平台层的通信,每个业务组件对应一个Channel,当然内部还可以进行更细粒度的Channel进行划分,这个Channel主要是负责Native层服务的提供,让Flutter层消费。而对于Native层调用Flutter层的Api,应该尽可能少,需要调也只有出现一些值回调时

因为Flutter的出现最本质的就是一次开发两端运行,而如果有太多这种依赖于平台层的实现,反而出现违背了,最后只是UI写了一份而已。对于平台层的实现也要尽量保持一个原则,即:

尽量让Native平台层成为服务层,让Flutter层成为消费层调用Native层的服务,即Dart调用Native的Api,这样当两端开发人员编写好一致基础的服务接口后,Flutter的开发人员即可平滑使用和开发

而对于基础组件中的公共服务组件Dart Api层的设计,因为公共服务主要调用Native层的服务,在Flutter中提供公共的Dart Api,作为Native到Flutter的一个桥梁,对于Native的服务,会有很有多种,而对应Api的设计为一个dart文件对应一个种类的服务,整个公共服务组件提供一个统一个对外暴露的Dart,内部的细粒度的Dart实现通过export导入,这种设计思想正是Flutter官方Api的设计,即统一对外暴露的Dart为common_service.dart

library common_service;

export 'network_plugin.dart';

export 'messager_plugin.dart';

...

而上层业务组件调用Api只需要import一个dart即可,这样对上层业务组件开发人员是透明的,上层不需要了解有哪些Api可用:

import 'package:common_service/common_service.dart';

八、Flutter混合开发工程化架构

基本组件化的架构我们搭建好了,接下来是如何让Flutter混合开发进行完整的工程化管理,我们都知道,对于官方的本地依赖这种方式,我们不能直接用,因为这会直接影响Native工程、开发流程与打包流程,所以我们得基于官方这种依赖方式进行优化改造,于是我们衍生出两种Flutter链接到Native工程的方式:

  1. 本地依赖(源码依赖)
  2. 远程依赖(产物依赖)

为什么要有这两种方式,首先本地依赖对于打包平台不支持,现有打包平台的环境,只能支持标准的Gradle工程结构进行打包,且本地依赖对于无需开发Flutter相关业务的同学来说是灾难性的,所以便有了远程依赖,远程依赖直接依赖于打包好的Flutter产物,Android通过Gradle依赖,IOS通过Pod远程依赖,这样对其它业务开发同学来说是透明的,他们无需关心Flutter也不需要知道Flutter是否存在

对于这两种依赖模式的使用环境也各不一样

1. 本地依赖 本地依赖主要用于需要进行Flutter开发的同学,通过在对应Native工程中配置文件配置是否打开本地Flutter Module依赖,以及配置链接的本地Flutter Module地址,这样Native工程即可自动依赖到本地的Flutter工程,整个过程是无缝的,同时本地依赖是通过源码进行依赖的,也可以很方便的进行Debug调试 对于Android中配置文件为本地的local.properties,IOS中为本地新建的local.xcconfig,两个平台的配置属性保持一致:

FLUTTER_MODULE_LINK_ENABLE=true

FLUTTER_MODULE_LOCAL_LINK=/Users/Sunzxyong/FlutterProject/flutter_module

2. 远程依赖 远程依赖是把Flutter Module的构成产物发布到远程,然后在Native工程中远程依赖,这种依赖方式是默认的依赖方式,这样对其它开发同学来说是透明的,不影响开发流程和打包平台

上述说到的两种依赖方式,接下来主要说怎么进行这两种依赖方式的工程化管理和定制化

1. 无侵入Flutter SDK源码进行BugFix和定制化

Flutter SDK在使用时,不免会遇到一些Flutter SDK的问题或Bug,但这些问题通常是在各平台层的链接脚本中出现坑,而如果我们要兼容现有工程和扩展定制化功能,往往会直接修改Flutter SDK源码,这种侵入性的方式极不推荐,这对后续SDK的平滑升级会带来更多的成本

通常出现Bug或需要定制化的脚本往往是和平台链接时相关的,当然排除需要修改dart层Api代码的情况下,这种只能更改源码了,不过这种出bug的几率还是比较小的,比较涉及到SDK的Api层面了。而大概率出现问题需要兼容或进行定制化的几个地方通常为下面几处:

  1. $FLUTTER_SDK/packages/flutter_tools/gradle/flutter.gradle
  2. $FLUTTER_SDK/bin/cache/artifacts/engine/android-arch/flutter.jar
  3. $FLUTTER_MODULE/.android/build.gradle、.android/settings.gradle
  4. $FLUTTER_MODULE/.android/Flutter/build.gradle
  5. $FLUTTER_MODULE/.ios/Flutter/Generated.xcconfig
  6. $FLUTTER_MODULE/.ios/Flutter/podhelper.rb
  7. $FLUTTER_MODULE/.ios/Podfile
  8. $FLUTTER_SDK/packages/flutter_tools/bin/xcode_backend.sh

而我们需要兼容的Flutter SDK的问题和定制化的点有下面几项:

  1. Android :Flutter SDK中的Flutter引擎不支持armeabi架构
  2. Android :Flutter SDK中的flutter.gradle链接脚本不支持非app名称的Application工程
  3. Android :Flutter SDK中的flutter.gradle链接脚本本地依赖存在flutter_shared资源文件不拷贝Bug
  4. Android :解决上述几项需要代理build.gradle构建脚本,以及在build.gradle构建脚本中定制化我们的构建产物收集Task
  5. IOS :Flutter Module中自动生成的.ios中的podhelper.rbruby脚本使用了Pod中的post_install方法,导致Native工程不能使用或使用了的发生冲突,间接侵入了Native工程与耦合,限制性太强
  6. IOS :Flutter Module中自动生成的Podfile文件,需要添加我们自己私有的Specs仓库进行定制化
  7. IOS :解决post_install问题后,Flutter SDK中的xcode_backend.sh链接脚本环境变量的读取问题

为了实现无侵入Flutter SDK,对于上述的这些问题的解决,我们使用代理方式进行Bug的修改和定制化,下面是针对两个平台分别的实现策略

1. Android

在Android平台上述问题和定制化的解决策略,对于armeabi架构的支持,我们可以通过脚本进行自动化,上面讲到flutterw的版本自动化管理,同样,我们在里面加段armeabi架构的支持脚本,这样做得好处是后续不需要支持了可以直接移除,通过调用./flutterw armeabi即可自动添加armeabi架构的引擎

对于Flutter SDK中的flutter.gradle链接脚本的问题兼容,不会直接在源码中进行更改,而是把它拷贝出来,命名为flutter_proxy.gradle,然后在代理脚本中进行问题的修复,主要修复点为flutter_shared的支持与app硬编码名称的兼容,如下:

Task copySharedFlutterAssetsTask = project.tasks.create(name: "copySharedFlutterAssetsKaTeX parse error: Expected '}', got 'EOF' at end of input: ...d/*' into "src/{variant.name}"

}

再让copyFlutterAssetsTask任务依赖于它,而app硬编码名称的兼容,则更简单了,通过在Native工程中local.properties配置Module名,再在flutter_proxy.gradle脚本中加入读取该属性代码:

String appName = loadRootProjectProperty(project, "FLUTTER_APP_NAME", "app")

Task mergeAssets = project.tasks.findByPath(": a p p N a m e : m e r g e {appName}:merge appName:merge{variant.name.capitalize()}Assets")

而对于build.gradle构建脚本的代理,我们可以通过在执行Gradle构建时,通过-c命令进行settings.gradle的代理,进而代理掉build.gradle和指定Module中的build.gradle脚本,如下:

cd .android

./gradlew assembleDebug -c .../script/proxy/settings.gradle

而通过代理的settings.gradle文件再进行build.gradle的代理:

getRootProject().buildFileName = 'build_proxy.gradle'

project(":flutter").buildFileName = "build_proxy.gradle"

其中代理的Flutter/build.gradle中的脚本apply会改为修复的Flutter SDK中的脚本代理:

apply from: "${project.projectDir.parentFile.parentFile.absolutePath}/script/proxy/flutter_proxy.gradle"

这样.android工程在构建时期可以完全由我们自主控制,包括加入一些产物收集插件、产物发布到远程插件等定制功能

不过这种方式需要执行构建命令时手动指定代理脚本,对于本地依赖时Native自动构建来说,是不会指定的,所有基于这种方式,我们再优化一下,因为Flutter Module.android.ios工程是通过Flutter SDK内部模版自动生成的,只要执行build|packages get等命令都会自动生成,首先想到是更改Flutter SDK内部工程模版,在Flutter SDK的packages/flutter_tools/templates目录下,不过这与我们无侵入Flutter SDK违背了,所以不能选取这种方式

回想我们的Flutter SDK版本一致性管理是通过flutterw脚本进行自动化的,而最终会链接调用到原生Flutter SDK中的命令,所以,我们可以在flutterw中加入脚本,用于在.android.ios工程生成后,进行内部脚本文件的替换,把build.gradlesettings.gradle脚本内容直接替换为我们的代理脚本的内容,这样既不侵入Flutter SDK,在后续维护起来也方便,后续不需要这个功能了,只需要把这段脚本代码注释就好了,随即又恢复原生的构建脚本了,flutterw脚本执行过程如下:

function main() {

...

link_flutter "$@"

inject_proxy_build_script

...

}

inject_proxy_build_script这个Shell函数会把对应脚本进行我们的脚本替换掉,当前函数内部也有对应判断,因为flutterw主要用于Flutter SDK版本一致性管理,这里仅对Flutter Module工程生效。所以这种方式不管是在本地依赖构建下还是通过命令行构建都可以完美支持

2. IOS

在IOS平台上述问题和定制化的解决策略,对于IOS主要是对Podfilepodhelper.rb脚本进行支持,而对Podfile的支持,这个比较简单,在Podfile头部通过脚本注入我们自己私有的Specs仓库即可:

source 'https://***/XXSpecs.git'

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '8.0'

...

这个工作同样在flutterw执行后进行兼容,后续不需要了可以直接注释,这个自动注入脚本也仅对Flutter Module工程生效

podhelper.rb脚本的兼容,主要是在进行本地依赖时,内部已经用了post_install函数,该函是在pod install后执行,这会与Native已经使用了该函数的发生冲突并报错,所以我们通过flutterw脚本的执行后默认注释掉该脚本中的post_install使用处,但是肯定不能平白无故注释掉,我们要了解这段的作用,其实就是设置环境变量,为后续xcode_backend.sh脚本的构建执行做准备,而注释掉怎么用另外一种方式恢复环境变量的设置这个后面再讲,注释后podhelper.rb脚本代码片段为:

post_install do |installer|

installer.pods_project.targets.each do |target|

target.build_configurations.each do |config|

config.build_settings['ENABLE_BITCODE'] = 'NO'

xcconfig_path = config.base_configuration_reference.real_path

File.open(xcconfig_path, 'a+') do |file|

file.puts "#include "#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}""

end

end

end

end

最终在flutterw自动支持上述处理脚本执行流程为:

function main() {

...

link_flutter "$@"

...

podfile_support

podhelper_support

collect_ios_product "$@"

}

函数内部判断仅针对Flutter Module工程生效,毕竟其它Flutter Plugin工程不需要这种处理

2. 本地依赖无侵入流程

我们要做到只通过一个属性配置文件,在配置文件中通过配置开发来打开或关闭本地的Flutter Module链接依赖,只按官方的依赖方式肯定是不行的,不管是Android还是IOS,都会直接侵入Native工程,影响其它无Flutter环境同学的开发且影响打包平台上的打包。所以,肯定得做优化,我们在官方这种依赖方式中加一层,作为代理层,而代理层主要做的工作是判断本地是否有对应的属性配置文件且属性值是否符合本地依赖Flutter Module的条件,如果是则进行本地Flutter Module的依赖,如果不是则Return掉,默认不做任何处理

所以通过这种代理方式即不影响Native工程原先的开发流程,对其它业务开发同学和打包平台也是透明的

对于代理层的实现,Android与IOS平台各不一样

1. Android

Android是通过一个Gradle脚本进行自动管理的,这个Gradle脚本主要在settings.gradlebuild.gradle中做local.properties配置文件的属性值校验,决定是否开启本地Flutter Module链接的

2. IOS

IOS则较为复杂一些,因为涉及到Podfile中的ruby执行脚本代理与Build Phases时期的Shell脚本代理,所以得写两种类型的代理脚本:Ruby和Shell,代理脚本的最终执行还是会调用被代理的脚本,只是在调用前做一层包装逻辑判断。而IOS中本身没有本地配置文件,所以我们新建一个IOS的本地配置文件为local.xcconfig,这个配置文件不随版本进行管理,会gitignore掉,于是,在IOS中Podfile最终调用的脚本是:

eval(File.read(File.join('./', 'FlutterSupport', 'podhelper_proxy.rb')), binding)

而在Build Phases调用的是:

chmod +x " S R C R O O T / F l u t t e r S u p p o r t / x c o d e b a c k e n d p r o x y . s h " " {SRCROOT}/FlutterSupport/xcode_backend_proxy.sh" " SRCROOT/FlutterSupport/xcodebackendproxy.sh""{SRCROOT}/FlutterSupport/xcode_backend_proxy.sh" flutterBuild

而刚刚上面说到的podhelper.rb脚本中post_install函数被注释掉后怎么用另一种方式进行替换,我们知道这段函数主要就是提供在IOS构建阶段时执行xcode_backend.sh的环境变量的,比如会获取FLUTTER_ROOT等属性值,这些环境变量由Flutter Module中Generated.xcconfig来提供,而如果我们把这个文件的内容通过脚本拷贝到IOS工程下对应构建配置的xcconfig中,如debug.xcconfigrelease.xcconfig,这种方式可行,不过会侵入Native工程,导致Native工程中多了这些变量,而且不优雅,我们要做到的是保证无侵入性

既然我们已经通过代理脚本进行代理,那么这些环境变量我们完全可以获取出来,通过Shell脚本的特性,子Shell会继承于父Shell中export的环境变量值,所以,在代理Shell脚本中再加段下面代码:

function export_xcconfig() {

export ENABLE_BITCODE=NO

if [[ $# != 0 ]]; then

local g_xcconfig= 1 / . i o s / F l u t t e r / G e n e r a t e d . x c c o n f i g i f [ [ − f " 1/.ios/Flutter/Generated.xcconfig if [[ -f " 1/.ios/Flutter/Generated.xcconfigif[[−f"g_xcconfig" ]]; then

no piping.

while read -r line

do

if [[ ! " l i n e " = / / ] ] ; t h e n e x p o r t " line" =~ ^// ]]; then export " line"= //]];thenexport"line"

fi

done < $g_xcconfig

fi

fi

}

其中注意不能使用管道,管道会在另外一个Shell进程

3. 远程依赖产物打包流程

Flutter的远程产物依赖,Android是通过Aar依赖,IOS是通过.a.framework静态库进行依赖,要进行这些远程依赖很简单,关键是如何打包获取这些依赖的产物以及上传到远程,因为按照现有组件化的打包,除了聚合层Flutter Module中有对应的flutter-debug.aarApp.frameworkflutter_assets等产物的生成,其中业务组件和基础组件中,也有对应的打包产物,这些打包产物会对应各自平台打包不同类型产物,Android还是aar,而IOS则是.a静态库了,下面就分别讲下Android与IOS的打包流程

1. Android

Android的打包比较简单,通过在Flutter Module中的.android子工程下执行./gradlew assembleRelease,则会在对应Flutter中Android子工程的build目录下输出对应aar产物,而重点是怎么获取依赖的各组件(Flutter Plugin)中的产物,则是通过.flutter-plugins文件,该文件是在packages get时自动生成的,里面包含了该Flutter工程通过Pub所依赖的库,我们可以解析这个文件,来获取对应依赖库的产物

2. IOS

IOS上的打包相比Android来说更复杂一些,我们借助.ios/Runner来打包出静态库等产物,所以还需要设置签名,通过在Flutter Module中直接执行./flutterw build ios --release,该命令会自动执行pod install,所以我们不必再单独执行它,IOS中构建出的产物获取也相对繁琐些,除了获取Flutter的相关产物,还需要获取所依赖的各组件的静态库以及头文件,需要获取的产物如下:

Flutter.framework App.framework FlutterPluginRegistrant flutter_assets 所有依赖的Plugin的.a静态库以及头文件

其中Flutter.framework为Flutter引擎,类似Android中的flutter.so,而App.framework则是Flutter中Dart编译后的产物(Debug模式下它仅为一个空壳,具体Dart代码在flutter_assets中,Release模式下为编译后的机器指令),FlutterPluginRegistrant是所有插件Channel的注册表,也是自动生成的,flutter_assets含字体等资源,剩下一些.a静态库则是各组件在IOS平台层的实现了

而收集IOS产物除了在.ios/Flutter目录下收集*.framework静态库和flutter_assets外,剩下的就是收集.a静态库以及对应的头文件了,而这些产物则是在构建Runner工程后,在Flutter Module下的

build/ios/$variant-iphoneos

目录下,variant对应所构建变体名,我们还是通过解析.flutter-plugins文件,来获取对应所依赖Flutter插件的名称,进而在上述的输出目录下找到对应的.a静态库,但是对应的头文件而不在对应.a静态库目录下,所以对于头文件单独获取,因为解析了.flutter-plugins获取到了KV键值对,对应的V则是该Flutter插件工程地址,所以头文件我们从里面获取

最后还需要获取FlutterPluginRegistrant注册表的静态库以及头文件

3. 产物收集与传递依赖

对于通过Flutter Module聚合层构建出来的产物,我们进行收集后再聚合到单独的产物输出目录下,当然这一切都是通过脚本自动做掉的

在Android上,通过Gradle插件Hook assembleTask

collectAarTask.dependsOn assembleTask

assembleTask.finalizedBy collectAarTask

这样当执行完./gradlew assemble${variant}命令后则会自动进行产物收集

在IOS上,通过flutterw脚本,在构建完后判断构建命令是否是IOS构建命令,进而自动收集构建后的产物:

function collect_ios_product() {

if [[ # != 0 \&\& # > 2 ]]; then

if [[ "1" = "build" \&\& "2" = "ios" ]]; then

do collect...

fi

fi

}

对应.a静态库和头文件的收集关键脚本代码如下:

while read -r line

do

if [[ ! "KaTeX parse error: Expected 'EOF', got '&' at position 14: line" =~ ^// &̲& ! "line" =~ ^# ]]; then

array=( l i n e / / = / ) l o c a l l i b r a r y = {line//=/ }) local library= line//=/)locallibrary=product_dir/ a r r a y [ 0 ] / l i b {array[0]}/lib array[0]/lib{array[0]}.a

if [[ -f " l i b r a r y " ] ] ; t h e n l o c a l p l u g i n = library" ]]; then local plugin= library"]];thenlocalplugin=dest_dir/plugins/${array[0]}

rm -rf $plugin

mkdir -p $plugin

cp -f $library p l u g i n l o c a l c l a s s e s = plugin local classes= pluginlocalclasses={array[1]}ios/Classes

for header in find "$classes" -name *.h; do

cp -f $header p l u g i n d o n e − r l i n e d o i f [ [ ! " plugin done -r line do if [[ ! " plugindone−rlinedoif[[!"line" =~ ^// && ! "KaTeX parse error: Expected group after '^' at position 10: line" =~ ^̲# ]]; then arra...{line//=/ })

local library= p r o d u c t d i r / product_dir/ productdir/{array[0]}/lib a r r a y [ 0 ] . a i f [ [ − f " {array[0]}.a if [[ -f " array[0].aif[[−f"library" ]]; then

local plugin= d e s t d i r / p l u g i n s / dest_dir/plugins/ destdir/plugins/{array[0]}

rm -rf $plugin

mkdir -p $plugin

cp -f $library p l u g i n l o c a l c l a s s e s = plugin local classes= pluginlocalclasses={array[1]}ios/Classes

for header in find "$classes" -name *.h; do

cp -f header plugin

done

相关推荐
candyTong4 小时前
一觉醒来,大模型就帮我排查完页面性能问题
前端·javascript·架构
空中海6 小时前
Kubernetes 入门基础与核心架构
贪心算法·架构·kubernetes
米高梅狮子8 小时前
08.CronJob和Service
云原生·容器·架构·kubernetes·自动化
maaath8 小时前
【maaath】Flutter for OpenHarmony 跨平台工程集成密码加密能力
flutter·华为·harmonyos
yeziyfx8 小时前
Flutter 纯色矩形
flutter
liulian09169 小时前
Flutter for OpenHarmony 混合开发实践:用户反馈功能的实现与适配
flutter·华为·学习方法·harmonyos
SamDeepThinking9 小时前
中小团队需要一个资源微服务
后端·微服务·架构
两万五千个小时9 小时前
为什么你的 Agent 读了文件,却好像什么都没读到?
人工智能·程序员·架构
非优秀程序员10 小时前
智能体的构成--深入探讨Anthropic、OpenAI、Perplexity和LangChain究竟在构建什么。
人工智能·架构·开源
Hello__777710 小时前
开源鸿蒙 Flutter 实战|文章分类标签功能全流程实现
flutter·开源·harmonyos