苏州建设公司网站建设,网页qq登录保护怎么关闭,谷歌搜索引擎下载,中铁三局招聘学历要求2019独角兽企业重金招聘Python工程师标准 一、背景 在日常开发中#xff0c;我们经常会有发布需求#xff0c;而且还会遇到各种环境#xff0c;比如#xff1a;线上环境#xff08;Online#xff09;#xff0c;模拟环境#xff08;Staging#xff09; 一、背景 在日常开发中我们经常会有发布需求而且还会遇到各种环境比如线上环境Online模拟环境Staging开发环境Dev等。最简单的就是手动构建、上传服务器但这种方式太过于繁琐使用持续集成可以完美地解决这个问题推荐了解一下Jenkins。 Jenkins构建也有很多种方式现在使用比较多的是自由风格的软件项目Jenkins构建的一种方式会结合SCM和构建系统来构建你的项目甚至可以构建软件以外的系统的方式。针对单个项目的简单构建这种方式已经足够了但是针对多个类似且又存在差异的项目就难以满足要求否则就需要大量的job来支持这就存在一个小的变动就需要修改很多个job的情况难以维护。我们团队之前就存在这样的问题。 目前我们团队主要负责开发和维护多个Android项目而且每个项目都需要构建每个构建流程非常类似但又存在一定的差异。比如构建的流程大概如下 克隆代码静态代码检查可选单元测试可选编译打包APK或者热补丁APK分析获取版本号VersionCode包的Hash值apkhash等加固上传测试分发平台存档可选触发自动化测试可选通知负责人构建结果等。整个流程大体上是相同的但是又存在一些差异。比如有的构建可以没有单元测试有的构建不用触发自动化测试而且构建结果通知的负责人也不同。如果使用自由风格软件项目的普通构建每个项目都要建立一个job来处理流程可能会调用其他job。 这种处理方式原本也是可以的但是必须考虑到可能会有新的流程接入比如二次签名构建流程也可能存在Bug等多种问题。无论哪种情况一旦修改主构建流程每个项目的job都需要修改和测试就必然会浪费大量的时间。针对这种情况我们使用了Pipeline的构建方式来解决。 当然如果有项目集成了React Native还需要构建JsBundle。在Native修改以后JsBundle不一定会有更新如果是构建Native的时候一起构建JsBundle就会造成很多资源浪费。并且直接把JsBundle这类大文件放在Native的Git仓库里也不是特别合适。 本文是分享一种Pipeline的使用经验来解决这类问题。 二、Pipeline的介绍 Pipeline也就是构建流水线对于程序员来说最好的解释是使用代码来控制项目的构建、测试、部署等。使用它的好处有很多包括但不限于 使用Pipeline可以非常灵活的控制整个构建过程可以清楚的知道每个构建阶段使用的时间方便构建的优化构建出错使用stageView可以快速定位出错的阶段一个job可以搞定整个构建方便管理和维护等。Stage View 三、使用Pipeline构建 新建一个Pipeline项目写入Pipeline的构建脚本就像下面这样: 对于单个项目来说使用这样的Pipeline来构建能够满足绝大部分需求但是这样做也有很多缺陷包括 多个项目的Pipeline打包脚本不能公用导致一个项目写一份脚本维护比较麻烦。一个变动需要修改多个job的脚本多个人维护构建job的时候可能会覆盖彼此的代码修改脚本失败以后无法回滚到上个版本无法进行构建脚本的版本管理老版本发修复版本需要构建可能和现在用的job版本已经不一样了等等。四、把Pipeline当代码写 既然存在缺陷我们就要找更好的方式其实Jenkins提供了一个更优雅的管理Pipeline脚本的方式在配置项目Pipeline的时候选择Pipeline script from SCM就像下面这样 这样Jenkins在启动job的时候首先会去仓库里面拉取脚本然后再运行这个脚本。在脚本里面我们规定的构建方式和流程就会按部就班地执行。构建的脚本可以实现多人维护还可以Review避免出错。 以上就算搭建好了一个基础而针对多个项目时还有一些事情要做不可能完全一样以下是构建的结构图 如此以来我们的构建数据来源分为三部分job UI界面、仓库的通用Pipeline脚本、项目下的特殊配置我们分别来看一下 job UI界面参数化构建 在配置job的时候选择参数化构建过程传入项目仓库地址、分支、构建通知人等等。还可以增加更多的参数 这些参数的特点是可能需要经常修改比如灵活选择构建的代码分支。 项目配置 在项目工程里面放入针对这个项目的配置一般是一个项目固定不经常修改的参数比如项目名字如下图 注入构建信息 QA提一个Bug我们需要确定这是哪次的构建或者要知道commitId从而方便进行定位。因此在构建时可以把构建信息注入到APK之中。 把属性注入到gradle.properties# 应用的后端环境
APP_ENVBeta
# CI 打包的编号方便确定测试的版本不通过 CI 打包默认是 0
CI_BUILD_NUMBER0
# CI 打包的时间方便确定测试的版本不通过 CI 打包默认是 0
CI_BUILD_TIMESTAMP0在build.gradle里设置buildConfigField#使用的是gradle.properties里面注入的值
buildConfigField String, APP_ENV, \${APP_ENV}\
buildConfigField String, CI_BUILD_NUMBER, \${CI_BUILD_NUMBER}\
buildConfigField String, CI_BUILD_TIMESTAMP, \${CI_BUILD_TIMESTAMP}\
buildConfigField String, GIT_COMMIT_ID, \${getCommitId()}\//获取当前Git commitId
String getCommitId() {try {def commitId git rev-parse HEAD.execute().text.trim()return commitId;} catch (Exception e) {e.printStackTrace();}
}显示构建信息 在App里找个合适的位置比如开发者选项里面把刚才的信息显示出来。QA提Bug时要求他们把这个信息一起带上mCIIdtv.setText(String.format(CI 构建号:%s, BuildConfig.CI_BUILD_NUMBER));
mCITimetv.setText(String.format(CI 构建时间:%s, BuildConfig.CI_BUILD_TIMESTAMP));
mCommitIdtv.setText(String.format(Git CommitId:%s, BuildConfig.GIT_COMMIT_ID));仓库的通用Pipeline脚本 通用脚本是抽象出来的构建过程遇到和项目有关的都需要定义成变量再从变量里进行读取不要在通用脚本里写死。
node {try{stage(检出代码){//从git仓库中检出代码git branch: ${BRANCH},credentialsId: xxxxx-xxxx-xxxx-xxxx-xxxxxxx, url: ${REPO_URL}loadProjectConfig();}stage(编译){//这里是构建你可以调用job入参或者项目配置的参数比如echo 项目名字 ${APP_CHINESE_NAME}//可以判断if (Boolean.valueOf(${IS_USE_CODE_CHECK})) {echo 需要静态代码检查} else {echo 不需要静态代码检查}}stage(存档){//这个演示的Android的项目实际使用中请根据自己的产物确定def apk getShEchoResult (find ./lineup/build/outputs/apk -name *.apk)def artifactsDirartifacts//存放产物的文件夹sh mkdir ${artifactsDir}sh mv ${apk} ${artifactsDir}archiveArtifacts ${artifactsDir}/*}stage(通知负责人){emailext body: 构建项目:${BUILD_URL}\r\n构建完成, subject: 构建结果通知【成功】, to: ${EMAIL}}} catch (e) {emailext body: 构建项目:${BUILD_URL}\r\n构建失败\r\n错误消息${e.toString()}, subject: 构建结果通知【失败】, to: ${EMAIL}} finally{// 清空工作空间cleanWs notFailBuild: true}}// 获取 shell 命令输出内容
def getShEchoResult(cmd) {def getShEchoResultCmd ECHO_RESULT${cmd}\necho \${ECHO_RESULT}return sh (script: getShEchoResultCmd,returnStdout: true).trim()
}//加载项目里面的配置文件
def loadProjectConfig(){def jenkinsConfigFile./jenkins.groovyif (fileExists(${jenkinsConfigFile})) {load ${jenkinsConfigFile}echo 找到打包参数文件${jenkinsConfigFile}加载成功} else {echo ${jenkinsConfigFile}不存在,请在项目${jenkinsConfigFile}里面配置打包参数sh exit 1}
} 轻轻的点两下Build with Parameters - 开始构建然后等几分钟的时间就能够收到邮件。 五、其他构建结构 以上仅仅是针对我们当前遇到问题的一种不错的解决方案可能并不完全适用于所有场景但是可以根据上面的结构进行调整比如 根据stage拆分出不同的Pipeline脚本这样方便CI的维护一个或者几个人维护构建中的一个stage把构建过程中的stage做成普通的自由风格的软件项目的job把它们作为基础服务在Pipeline中调用这些基础服务等。六、当遇上React Native 当项目引入了React Native以后因为技术栈的原因React Native的页面是由前端团队开发但容器和原生组件是Android团队维护构建流程也发生了一些变化。 方案对比 方案说明缺点优点手动拷贝等JsBundle构建好了再手动把构建完成的产物拷贝到Native工程里面1. 每次手动操作比较麻烦效率低容易出错br /2. 涉及到跨端合作每次要去前端团队主动拿JsBundlebr /3. Git不适合管理大文件和二进制文件简单粗暴使用submodule保存构建好的JsBundle直接把JsBundle放在Native仓库的一个submodule里面由前端团队主动更新每次更新Native的时候直接就拿到了最新的JsBundle1. 简单无开发成本br /2. 不方便单独控制JsBundle的版本br /3. Git不适合管理大文件和二进制文件前端团队可以主动更新JsBundle使用submodule管理JsBundle的源码直接把JsBundle的源码放在Native仓库的一个submodule里面由前端团队开发更新每次构建Native的时候先构构建JsBundle1. 不方便单独控制JsBundle的版本br /2. 即使JsBundle无更新也需要构建构建速度慢浪费资源方便灵活分开构建产物存档JsBundle和Native分开构建构建完了的JsBundle分版本存档Native构建的时候直接去下载构建好了的JsBundle版本1. 通过配置管理JsBundle解放Gitbr /2. 方便Jenkins构建的时候动态配置需要的JsBundle版本1. 需要花费时间建立流程br /2. 需要开发Gradle的JsBundle下载插件前端团队开发页面构建后生成JsBundleAndroid团队拿到前端构建的JsBundle一起打包生成最终的产物。 在我们开发过程中JsBundle修改以后不一定需要修改NativeNative构建的时候也不一定每次都需要重新构建JsBundle。并且这两个部分由两个团队负责各自独立发版构建的时候也应该独立构建不应该融合到一起。 综合对比我们选择了使用分开构建的方式来实现。 分开构建 因为需要分开发布版本所以JsBundle的构建和Native的构建要分开使用两个不同的job来完成这样也方便两个团队自行操作避免相互影响。 JsBundle的构建也可以参考上文提到的Pipeline的构建方式来做这里不再赘述。 在独立构建以后怎么才能组合到一起呢我们是这样思考的JsBundle构建以后分版本的储存在一个地方供Native在构建时下载需要版本的JsBundle大致的流程如下 这个流程有两个核心一个是构建的JsBundle归档存储一个是在Native构建时去下载。 JsBundle归档存储 方案缺点优点直接存档在Jenkins上面1. JsBundle不能汇总浏览br2. Jenkins很多人可能要下载命名带有版本号时间分支等命名不统一不方便构建下载地址br3. 下载Jenkins上面的产物需要登陆授权比较麻烦1. 实现简单一句代码就搞定成本低自己构建一个存储服务1. 工程大开发成本高br2. 维护起来麻烦可扩展灵活性高MSSbr(美团存储服务)无1. 储存空间大br2. 可靠性高配合CDN下载速度快br3. 维护成本低 价格便宜这里我们选择了MSS。 上传文件到MSS可以使用s3cmd但毕竟不是每个Slave上面都有安装通用性不强。为了保证稳定可靠这里基于MSS的SDK写个小工具即可比较简单几行代码就可以搞定。 private static String TenantId mss_TenantId;
private static AmazonS3 s3Client;public static void main(String[] args) throws IOException {if (args null || args.length ! 3) {System.out.println(请依次输入inputFile、bucketName、objectName);return;}s3Client AmazonS3ClientProvider.CreateAmazonS3Conn();uploadObject(args[0], args[1], args[2]);
}public static void uploadObject(String inputFile, String bucketName, String objectName) {try {File file new File(inputFile);if (!file.exists()) {System.out.println(文件不存在 file.getPath());return;}s3Client.putObject(new PutObjectRequest(bucketName, objectName, file));System.out.printf(上传%s到MSS成功: %s/v1/%s/%s/%se, inputFile, AmazonS3ClientProvider.url, TenantId, bucketName, objectName);} catch (AmazonServiceException ase) {System.out.println(Caught an AmazonServiceException, which means your request made it to Amazon S3, but was rejected with an error response for some reason.);System.out.println(Error Message: ase.getMessage());System.out.println(HTTP Status Code: ase.getStatusCode());System.out.println(AWS Error Code: ase.getErrorCode());System.out.println(Error Type: ase.getErrorType());System.out.println(Request ID: ase.getRequestId());} catch (AmazonClientException ace) {System.out.println(Caught an AmazonClientException, which means the client encountered an internal error while trying to communicate with S3, such as not being able to access the network.);System.out.println(Error Message: ace.getMessage());}
}我们直接在Pipeline里构建完成后调用这个工具就可以了。 当然JsBundle也分类型在调试的时候可能随时需要更新这些JsBundle不需要永久保存一段时间后就可以删除了。在删除时可以参考MSS生命周期管理。所以我们在构建JsBundle的job里添加一个参数来区分。 //根据TYPE上传到不同的bucket里面
def bucket rn-bundle-prod
if (${TYPE} dev) {bucket rn-bundle-dev //有生命周期管理一段时间后自动删除
}
echo 开始JsBundle上传到MSS
//jar地址需要替换成你自己的
sh curl -s -S -L http://s3plus.sankuai.com/v1/mss_xxxxx/rn-bundle-prod/rn.bundle.upload-0.0.1.jar -o upload.jar
sh java -jar upload.jar ${archiveZip} ${bucket} ${PROJECT}/${targetZip}
echo 上传JsBundle到MSS:${archiveZip}Native构建时JsBundle的下载 为了实现构建时能够自动下载我们写了一个Gradle的插件。 首先要在build.gradle里面配置插件依赖 classpath com.zjiecode:rn-bundle-gradle-plugin:0.0.1在需要的Module应用插件 apply plugin: mt-rn-bundle-download在build.gradle里面配置JsBundle的信息 RNDownloadConfig {//远程文件目录,因为有多种类型所以这里可以填多个。paths [http://msstest-corp.sankuai.com/v1/mss_xxxx/rn-bundle-dev/xxx/,http://msstest-corp.sankuai.com/v1/mss_xxxx/rn-bundle-prod/xxx/]version 1//版本号这里使用的是打包JsBundle的BUILD_NUMBERfileName xxxx.android.bundle-%s.zip //远程文件的文件名,%s会用上面的version来填充outFile xxxx/src/main/assets/JsBundle/xxxx.android.bundle.zip // 下载后的存储路径相对于项目根目录
}插件会在package的task前面插入一个下载的tasktask读取上面的配置信息在打包阶段检查是否已经存在这个版本的JsBundle。如果不存在就会去归档的JsBundle里下载我们需要的JsBundle。 当然这里的version可以使用上文介绍的注入构建信息的方式通过job参数的方式进行注入。这样在Jenkins构建Native时就可以动态地填写需要JsBundle的版本了。 这个Gradle插件我们已经放到到了github仓库你可以基于此修改当然也欢迎PR。 https://github.com/zjiecode/rn-bundle-gradle-plugin 六、总结 我们把一个构建分成了好几个部分带来的好处如下 核心构建过程只需要维护一份减轻维护工作方便多个人维护构建CI避免Pipeline代码被覆盖方便构建job的版本管理比如要修复某个已经发布的版本可以很方便切换到发布版本时候用的Pipeline脚本版本每个项目配置也比较灵活如果项目配置不够灵活可以尝试定义更多的变量构建过程可视化方便针对性优化和错误定位等。当然Pipeline也存在一些弊端比如 语法不够友好但好在Jenkins提供了一个比较强大的帮助工具Pipeline Syntax代码测试繁琐没有本地运行环境每次测试都需要提交运行一个job等等。当项目集成了React Native时配合Pipeline我们可以把JsBundle的构建产物上传到MSS归档。在构建Native的时候 可以动态地下载。 七、作者 张杰美团点评高级Android工程师2017年加入餐饮平台成都研发中心主要负责餐饮平台B端应用开发。 王浩美团点评高级Android工程师2017年加入餐饮平台成都研发中心主要负责餐饮平台B端应用开发。 八、招聘广告 本文作者来自美团成都研发中心是的我们在成都建研发中心啦。我们在成都有众多后端、前端和测试的岗位正在招人欢迎大家投递简历songyanweimeituan.com。 转载于:https://my.oschina.net/meituantech/blog/1922037