基于Dart开发的iOS组件发布小助手

基于Dart开发的命令行工具,不仅仅支持跨平台,而且编译的成果物可以直接运行,不需要配置Dart环境,刚好也在学习Flutter,可以拿来练练手

目前的问题

基线的iOS各个模块源码都在SVN上,每次发布组件挺麻烦的,版本号需要手动修改,还要先通过HiModule发布,导出Podspec,拖到Git仓库里面,再提交,非常的繁琐。尤其是在修改了多个组件,手动一个个发布的时候,不小心复制错了版本号,又得重新来。

解决思路

其实组件的发布流程大致如下:

  • SVN提交组件源码
  • 利用HiModule打Tag,(版本号还是需要自己手动输入)
  • HiModule导出修改版本号的Podspec文件
  • 将Podspec拖到Git的Develop目录,并且提交

上面这些步骤,其实使用脚本都可以直接完成,没必要使用HiModule一步步操作。

下面就是成果物的演示图:

代码实现

解决思路有了,就看代码实现了,整理了下大致流程如下:

  • 1.环境 + OA + Git 校验均通过
  • 2.获取输入的组件名,匹配是否有该组件,没有则提示终止程序
  • 3.通过组件名,查询当天该组件的提交记录,是否匹配当前OA账号,不匹配则提示退出
  • 4.获取历史tags下目录最新的版本号,并解析,生成新的版本
  • 5.通过新的版本号,给当前组件打tag,并导出该组件的Podspec,修改Podspec的版本为新的版本号
  • 6.将生产的新的Podspec导入到本地Git的Develop仓库,添加提交日志,并提交

使用命令创建命令行工程

dart create -t console-full flowcli

创建好的工程如下

简单介绍各个文件的功能

  • flowcli.dart - 工具类的入口,main函数就在这里,负责处理协调各个工具类的逻辑
  • AMConf.dart - 环境配置相关,OA 信息、SVN、Git仓库路径,以及解析配置文件的逻辑
  • AMGit.dart - Git相关操作的工具类
  • AMSVN.dart - SVN相关操作的工具类,大部分逻辑都在该类里面
  • AMTool.dart - 通用工具类,比如格式化输入log,格式化日期、时间戳等
#flow环境配置参数

OA: 
  name: 'xxxx'#OA账户ID
  passwd: 'xxxx'#OA账户密码

SVN: 
  SVNModuleURL: 'https://192.0.0.140/APP-Client/iVMS5260/trunk/HiModules/iOS' #源码仓库地址,也就是组内的iOS模块仓库地址

Git: 
  PodspecPath: '/Users/cxd/Develop/' #组件源码仓库本地路径,必须是Git克隆下的路径,不然没办法自动上传

CheckVersion: true #每次启动检测新版本

 

环境检测逻辑

由于SVN的操作需要OA密码。所以在flowcli工具首次初始化的时候,会在当前文件夹生成flow.conf,只有读取到OA信息和Git路径等,才会操作下一步。配置文件是YAML格式,方便解析。

在读取到OA信息之后,就会进行网络校验,判断当前网络环境是否为内网,主要通过访问oa.hikvision.com.cn,判断响应的状态,无法访问则提示当前非内网,流程终止

  // 网络检测
  static Future<bool> checkServer() async {
    try {
      var httpClient = HttpClient();
      var request =
          await httpClient.getUrl(Uri.parse('http://oa.hikvision.com.cn'));
      var response = await request.close();
      if (response.statusCode == HttpStatus.ok) {
        return true;
      } else {
        return false;
      }
    } on Exception {
      return false;
    } catch (_) {
      return false;
    }
  }

模块SVN 提交记录匹配

这里就是判断当前OA用户今天是否提交了代码,如果没提交的话,是不可以自动发布组件的。(你都没提交代码,你发布啥呢....)

  //判断当前用户当天是否提交过代码
  static Future<bool> getModuleLatestLog(
      String currentDay, String moduleName) async {
    var url = AMConf.conf.svnURL + '/' + moduleName;
    var args = ['log', '--search', currentDay, url];
    return await runCommand(args).then((value) {
      if (value is ProcessResult) {
        // 正常执行
        var date = args[2];
        String logs = value.stdout;
        // 如果日志为空,返回校验失败
        if (!logs.contains(date)) {
          return false;
        }
        // 如果日志不为空,判断当天该用户是否提交过代码
        if (logs.contains(AMConf.conf.oaName)) {
          return true;
        } else {
          return false;
        }
      } else if (value is ShellException) {
        // 异常处理
        print('异常处理:${value.result?.stderr}');
        return false;
      } else {
        return false;
      }
    });
  }

 

实现版本号自增

这里的版本号自增逻辑,我主要按2种类型,分别处理的

  • 第一种:1.5.7
  • 第二种:1.5.7.18-evzi
  • 第三种:1.5.7.20201019.2100

首先我将版本号按.分割成数组
然后判断数组位数,最后一位三位长度的以内的,数组倒叙遍历,拿9-该数,如果大于0,则遍历终止,最后一位+1,新的版本号就有了
如果最后一个位大于等于4,则删除最后一个元素,并将最后一个元素替换成20210829.0515样式

//版本号自增逻辑
  static Future<String?> generateModuleNewTag(String moduleName) async {
    return await getModuleLatestTag(moduleName).then((lastTag) {
      if (lastTag != null) {
        //版本号新增
        var tagArr = lastTag.split('.');
        var lastStr = tagArr.last;
        var number = AMTool.isNumber(lastStr);
        if (lastStr.length >= 4) {
          if (lastStr.length == 4 && number == true) {
            //最后一个字符串刚好是四位,且为纯数字,那肯定是时分时间戳
            tagArr.removeLast();
            tagArr.last = AMTool.currentTimestamp();
          } else if (lastStr.length == 8 && number == true) {
            //最后一个字符串大于四位,认定是年月日,仅替换最后一位即可
            tagArr.last = AMTool.currentTimestamp();
          } else {
            //版本号解析失败,用户指定
            AMTool.log('获取到上一次提交的版本号为$lastTag,无法自动解析生成新版本号,请手动输入新的版本号:',
                logLevel: AMLogLevel.AMLogWarn);
            var inputTag = stdin.readLineSync();
            if (inputTag!.isEmpty) {
              AMTool.log('新的版本号不能为空', logLevel: AMLogLevel.AMLogError);
              return null;
            }
            return inputTag;
          }
        } else {
          //非时间戳版本号
          var versionParse = true;
          for (var i = tagArr.length - 1; i >= 0; i--) {
            var item = int.tryParse(tagArr[i]);
            if (item != null) {
              //版本号解析成功,自增
              if (99 - (item + 1) > 0) {
                //版本号小于99,可以自增
                tagArr[i] = (item + 1).toString();
                break;
              } else {
                //版本号大于99,
                tagArr[i] = (0).toString();
              }
            } else {
              //解析失败,分两种处理,版本号最后一位非数字,替换成当前时间戳版本号,不是最后一位非数字,终止程序
              if (i == tagArr.length - 1) {
                tagArr[i] = AMTool.currentTimestamp();
                break;
              } else {
                versionParse = false;
              }
            }
          }
          //获取到处理的版本号
          if (!versionParse) {
            return null;
          }
        }
        return tagArr.join('.');
      } else {
        return null;
      }
    });
  }

 

以新的版本号创建tag

  static Future<bool> createTag(String moduleName, String newVersion) async {
    var trunkURL = AMConf.conf.svnURL + '/' + moduleName + '/trunk';
    var targetTagURL =
        AMConf.conf.svnURL + '/' + moduleName + '/tags/' + newVersion;
    var commitLog = '$moduleName 更新 $newVersion By flowcli 小助手';

    var args = [
      'cp',
      '--pin-externals',
      trunkURL,
      targetTagURL,
      '-m',
      commitLog
    ];
    return await runCommand(args).then((value) {
      if (value is ProcessResult) {
        if (value.exitCode == 0) {
          return true;
        } else {
          AMTool.log('创建SVN Tag 失败,错误:${value.stdout}');
          return false;
        }
      } else {
        AMTool.log('创建SVN Tag 失败,请检查SVN $moduleName 仓库');
        return false;
      }
    });
  }

 

导出Podspec,修改版本号,生成新的Podspec

static Future<bool> getModuleOldPodspecAndGenerateNew(
      String moduleName, String newVersion) async {
    var targetTagURL = AMConf.conf.svnURL +
        '/' +
        moduleName +
        '/tags/' +
        newVersion +
        '/$moduleName.podspec';
    var args = [
      'cat',
      targetTagURL,
    ];

    return await runCommand(args).then((value) {
      if (value is ProcessResult) {
        String con = value.stdout;
        if (con.contains('version')) {
          var newPodSpec;
          var podsList = con.split('\n');
          for (var item in podsList) {
            var ret = item.contains('s.version');
            if (ret == true) {
              newPodSpec =
                  con.replaceAll(item, "  s.version          = '$newVersion'");
              break;
            }
          }
          var filePath = AMConf.conf.gitLocalPath +
              '/$moduleName/$newVersion/$moduleName.podspec';
          var file = File(filePath);

          try {
            file.createSync(recursive: true);
            file.writeAsStringSync(newPodSpec);
          } catch (error) {
            AMTool.log('$filePath 路径创建失败,请检查Git仓库路径是否正确,是否有读写权限',
                logLevel: AMLogLevel.AMLogError);
            return false;
          }
          return true;
        }
      }
      return false;
    });
  }

 

Git发布Podspec

  static Future<bool> gitPush(String moduleName, String version) async {
    var cmd =
        "git add . \n git commit -m '[update]$moduleName($version) By flowcli 小助手' \n git push";
    return _shellTool.run(cmd).then((value) {
      if (value.length == 3) {
        var lastRet = value.last;
        if (lastRet.exitCode == 0) {
          return true;
        }
      }
      return false;
    }).catchError((error) {
      if (error is ShellException) {
        AMTool.log('组件Podspec 推送失败:${error.message}\n${error.result?.stderr}',
            logLevel: AMLogLevel.AMLogError);
      }
      return false;
    });
  }

总结

Dart 是一门非常容易上手的开发语言,又有很好的跨平台解决方案,唯一可惜的就是目前不支持交叉编译

源码在这里https://github.com/Amberler/iOSDevTool

版权声明:
作者:Amber
链接:https://late.run/archives/117
来源:LATE-努力努力再努力
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录