本文较长,废话描述较多,如不感兴趣,请直接翻到最后,获取代码地址。
解决什么问题
- 构建编译时无需开启各种编辑器
- 每个项目都可以通用
- 工作流程化
- 专注于游戏开发
技术方案
首先,其本质是命令行构建项目,使用CocosCreator命令构建脚本,使用gradlew构建安卓包等。
其次,所谓的自动化,是借助jenkins等自动化工具实现的,即触发式。
最后,流程化的意思是只有人员填写好配置即可。
因此,本方案采取的如下技术:
配置文件采用yaml:可读性强,相对json能添加注释。
使用nodejs处理:跨平台,npm库有非常多成熟的库可以拿来用,强大的“child_process”可以系统命令。
入口单一:可以自由选择jenkisn、命令行、服务器触发方式。
具体实现
配置模板
每个项目都根据配置进行构建打包,本方案将配置文件存放在settings文件夹下,推荐纳入版本管理。
1 2 3 4 5 6 7 8
|
webCode: 1 appCode: 1
title: 项目名称
|
yaml可以借助yamljs库快速转成json
1 2 3 4 5
| const yaml = require('yamljs'); function xxx(configPath){ let config = yaml.parse(fs.readFileSync(configPath).toString()); }
|
构建命令
1
| const cmd = `${enginePath} --path ${projectDir} --build "title=${title};......"
|
- build参数参考文档
- 为了兼容不同的CocosCreator版本,所以引擎路径不要写死。
执行命令
nodejs可以通过child_process模块下的方法,执行批处理,该模块下大致可以分为三类:
exec/execSync 执行批命令
execFile/execFileSycn 执行批命令文件
spawn/spawnSync 核心方法,,exec和execFile都是基于spawn封装的
sync则如其名,是对应方法的同步方法。
本方案是一种自动化的方案,理论上不应该关心中间状态,所以使用的是execSync和execFileSync,如果需要关心实时执行,则应该选择spwan。
所以,一般执行批命令为:
1 2 3 4
| const { execSync } = require("child_process"); function xxx(cmd){ execSync(cmd); }
|
处理构建结果
web构建
web版本构建结束后,我们可能要处理其适配问题,比如集成我的另外一个插件H5优化适配,那可能要设置一些参数,可以在这里进行。
原生构建
- 备份代码
原生打包,大多数都进行了混淆,那我们需要备份未混淆的代码。
CocosCreator本身是为我们备份了的,但是每个版本都会覆盖,所以我们需要另外备份下。
1 2 3 4
| const dstDir = path.join(buildDir, 'js backups (useful for debugging)'); const backupsDir = path.join(outputDir, title); copyFileSync(dstDir, backupsDir);
|
- 生成热更包
同时,可能我们集成了热更文件,需要生成一个热更包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| let manifest = { packageUrl: packageUrl, remoteManifestUrl: `${packageUrl} project.manifest`, remoteVersionUrl: `${packageUrl} version.manifest`, version: `${ver} `, assets: {} }
let files = getAllFiles(hotUpdateDir); files.forEach((filePath) => { const relative = encodeURI(path.relative(hotUpdateDir, filePath).replace(/\\/g, '/')); manifest.assets[relative] = { size: fs.statSync(filePath), md5: md5 } }
fs.writeFileSync(path.join(hotUpdateDir, 'project.manifest'), JSON.stringify(manifest, null, 0)); delete manifest.assets; fs.writeFileSync(path.join(hotUpdateDir, 'version.manifest'), JSON.stringify(manifest, null, 4));
|
然后将其上传到对应的存储平台,比如本方案提供的上传到cos
1 2
| const cmd = `coscmd upload - r ${hotUpdateDir} /hotUpdate/${configData.title} /${ver}`; execSync(cmd);
|
原生打包
原生构建后,还需要打出apk、ipa包。
制作icon
参考另一篇文章Node.js的图片处理库images
,我们可以在settings文件夹中,放入一张logo.png, 我们通过images库处理下就可以了。
1 2 3 4 5 6
| const images = require('images'); for(let i = 0; i < icons[i].length; i++){ images(path.join(configData.projectDir, 'settings', 'logo.png')) .size(icons[i].width) .save(path.join(iconPath, 'ic_launcher.png')); }
|
根据这个原理,我还制作了一个icon快速生成的小工具ICON生成器
删除game和instantapp项目
因为我们只需要原生apk项目,所以删除
1 2 3
| const sgPath = path.join(androidDir, 'settings.gradle'); let sgData = fs.readFileSync(sgPath).toString().replace(/\,[ ]*\'\:game\'[ ]*\,[ ]*\'\:instantapp\'/, ""); fs.writeFileSync(sgPath, sgData);
|
- 如果不删除,则打包命令修改,或要接入game、instantapp代码
修改gralde.properties
因为CocosCreator的命令行打包,有个bug(已知2.3.3版本存在),即gralde.properties的sdk会变成-1,所以我们需要修改下:
1 2 3 4 5 6 7 8 9 10
| const gpPath = path.join(androidDir, 'gradle.properties'); let gpData = fs.readFileSync(gpPath).toString(); gpData = gpData.replace(/PROP_COMPILE_SDK_VERSION=.*/, `PROP_COMPILE_SDK_VERSION=${configData.apiLevel}`); gpData = gpData.replace(/PROP_TARGET_SDK_VERSION=.*/, `PROP_TARGET_SDK_VERSION=${configData.apiLevel}`); gpData = gpData.replace(/PROP_APP_ABI=.*/, `PROP_APP_ABI=${JSON.stringify(configData.appABIs).replace('[', '').replace(']', '').replace(/\"/g, '').replace(/\,/g, ":")}`); gpData = gpData.replace(/RELEASE_STORE_FILE=.*/, `RELEASE_STORE_FILE=${storeFile}`); gpData = gpData.replace(/RELEASE_STORE_PASSWORD=.*/, `RELEASE_STORE_PASSWORD=${password}`); gpData = gpData.replace(/RELEASE_KEY_ALIAS=.*/, `RELEASE_KEY_ALIAS=${alias}`); gpData = gpData.replace(/RELEASE_KEY_PASSWORD=.*/, `RELEASE_KEY_PASSWORD=${keyPassword}`); fs.writeFileSync(gpPath, gpData);
|
升级gradle
CocosCreator配置的gradle版本较低,如果需要接入高版本的sdk,我们还需要升级gradle,如下为升级到3.6.4的操作
- gradle-wrapper.properties
1 2 3
| let gwpPath = path.join(androidDir, 'gradle', 'wrapper', 'gradle-wrapper.properties'); let gwpData = fs.readFileSync(gwpPath).toString().replace(/distributionUrl\=.*/, "distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.4-all.zip"); fs.writeFileSync(gwpPath, gwpData);
|
- 工程的build.gradle
1 2 3
| const pbgPath = path.join(androidDir, 'build.gradle'); let data = fs.readFileSync(pbgPath).toString().replace(/gradle\:[0-9.]+/, "gradle:3.6.4"); fs.writeFileSync(pbgPath, data);
|
- 修改mk
1 2 3
| const mkPath = path.join(androidDir, 'jni', 'CocosAndroid.mk'); let mkText = fs.readFileSync(mkPath).toString().replace('cocos2djs_shared', 'cocos2djs'); fs.writeFileSync(mkPath, mkText);
|
- 修改项目的build.gradle
本步骤主要是把复制语句的 “${outputDir}/xxx” 替换成 “outputDir.dir(xxx)”
1 2 3 4 5 6 7 8 9 10 11
| const abgPath = path.join(androidDir, 'app', 'build.gradle'); let gradleData = fs.readFileSync(abgPath).toString(); let matcher = /\"\$\{outputDir\}\/[a-zA-Z-]+\"/g; let matchs = gradleData.match(matcher); if (null != matchs) { for (let i = 0; i < matchs.length; i++) { let newStr = `outputDir.dir("${matchs[i].substring(matchs[i].indexOf('/') + 1, matchs[i].length - 1)}")`; gradleData = gradleData.replace(/\"\$\{outputDir\}\/[a-zA-Z-]+\"/, newStr); } } fs.writeFileSync(abgPath, gradleData);
|
修改项目的build.gradle
1 2 3 4 5 6 7 8 9
| const abgPath = path.join(androidDir, 'app', 'build.gradle'); let gradleData = fs.readFileSync(abgPath).toString(); let matcher1 = /applicationId[ ]+\"[a-zA-Z0-9_.]+\"/; gradleData = gradleData.replace(matcher1, `applicationId "${configData.packageName}"`); let matcher2 = /versionCode[ ]+[0-9]+/; gradleData = gradleData.replace(matcher2, `versionCode ${configData.appCode}`); let matcher3 = /versionName[ ]+\"[0-9.]+\"/; gradleData = gradleData.replace(matcher3, `versionName "${configData.appVer}"`); fs.writeFileSync(abgPath, gradleData);
|
执行安卓打包
1
| execFileSync('./gradlew', [':' + configData.title + ':assembleRelease'], { cwd: androidDir });
|
- cwd 表示当前路径切换到指定目录下执行命令。
- 这一步可能花费时间较久,如果想看实时状态,可以换用swpan
运行
至此,我们整个技术就实现了,然后我们用自动化工具,调用app.js并传入项目路径即可,参考提供的示例,可以直接使用批处理或终端,运行如下命令测试。
1
| node ./AutoPack/app.js -p ./PackTest
|
已知问题
- 受限于CocosCreator必须配合编辑器,目前不支持linxu平台。
- 构建IOS暂未实现。
- 构建安卓时,gradle不能和AndroidStudio共用(mac上是如此),需要自行配置全局目录,或者直接复制一份。
以上,基本无难点,但各种细节较多,比如各类插件、正则使用等,需要花点时间推敲。为减少各位操作步骤,公众号直接回复AutoPack即可获取完整代码,如果遇到什么问题,可以加我微信。
原文链接: https://blog.xyzzlky.cn/posts/1e18b962/
版权声明: 转载请注明出处.