基于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 是一门非常容易上手的开发语言,又有很好的跨平台解决方案,唯一可惜的就是目前不支持交叉编译
共有 0 条评论