CCNET+MSBuild+SVN实时构建的优化总结

本文不是介绍如何使用CCNET+MSBuild+SVN构建自动编译系统,相关的内容可以从很多地方获取,可以再园子里搜一下。

随着我们的SVN库日益壮大,容量达到10G,几十G 甚至更大时,我们发现自动构建速度越来越慢,直到有一天你发现入了很小一段代码却不得不等待几小时构建完成,程序员的忍受是有极限的,因此我们决定采取措施实施优化。

首先,我们必须分析哪些因素导致了我们构建速度的减慢,罗列一下,大概如下几个方面:

1. SVN库太大,使得构建服务器在更新SVN代码时花费大量时间。

2. SVN库里有很多工程,每当有SVN代码更新的时候,CCNET就会调用MSBuild将我们所有的工程都编译一遍。(即使入库的文件根本不需要编译,如python脚本)

3. SVN库中工程量越来越大,导致编译所有工程时间原来越长。

对于第三点,我们没有办法,但对于前两点,我们是有办法解决的,总结一下要做的事情:一是加快SVN更新速度,二是减少不必要的工程编译次数。

一、加快SVN更新速度

SVN的更新操作是有CCNET发起的,服务每隔一段时间查询一次SVN是否更新(看CCNET源码好像是调用svn --log来获取代码更新信息),如果有文件更新,则调用svn --update进行更新。从CCNET源码看来,CCNET对SVN代码的更新应该是针对性的,即,查询到哪部分代码有更新,就只更新那部分代码。这样的话效率应该不差。但在实际过程中,发现CCNET调用SVN更新速度异常的慢,甚至让我怀疑它是对整个SVN库执行了一次update操作。

要加快SVN更新速度,我们想到的是减少SVN更新的文件范围,假如你入库了一个python代码,或是QTP测试案例,因为无需编译,所以构建服务器甚至不需要更新那部分代码。因此,我们可以在CCNET的配置文件中只配置我们需要编译的工程:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><sourcecontrol type="multi">     <sourceControls>         <svn>             <trunkUrl>http://xxx/projectA</trunkUrl>             <workingDirectory>x:\ccnet\svn\projctA</workingDirectory>             <username>name</username>             <password>pwd</password>             <executable>x:\ccnet\Subversion\svn.exe</executable>         </svn>         <svn>             <trunkUrl>http://xxx/projectB</trunkUrl>             <workingDirectory>x:\ccnet\svn\projctB</workingDirectory>             <username>name</username>             <password>pwd</password>             <executable>x:\ccnet\Subversion\svn.exe</executable>         </svn>         <svn>             <trunkUrl>http://xxx/projectC</trunkUrl>             <workingDirectory>x:\ccnet\svn\projctC</workingDirectory>             <username>name</username>             <password>pwd</password>             <executable>x:\ccnet\Subversion\svn.exe</executable>         </svn>     </sourceControls> </sourcecontrol>

通过上面的设置,CCNET就是监视我们上面指定的SVN路径的代码更新了,如果你的SVN库中有大量不需要编译的文件,这样的优化带来的效果是巨大的。

二、减少编译次数

上面解决了对入库不需要编译的代码文件的问题,但我们还需要面临一个问题是,当你入库工程A的代码时,你只希望编译工程A,而不是将工程A,B,C都编译一遍。甚至,可能还有更加严格的要求。比如,我们库中有个公共库的工程FrameworkA,工程ProjectA,ProjectB,ProjectC都使用到了该公共库工程。我们希望做到:

1. 当我入库的代码属于FrameworkA时,希望把ProjectA,ProjectB,ProjectC都编译一遍。(因为我修改了公共库,很有可能导致工程A,B,C编译不过。)

2. 当我入库的是ProjectA(或B,C)时,我只希望编译ProjectA(或B,C)就行了。

我们看到我们的工程之间多了一些内在的联系,如何才能处理这种复杂的编译关系呢?我想到的是,要么在CCNET上做手脚,要么在MSBuild上进行扩展。CCNET是一个开源项目,我完全可以修改它的代码为我所用,甚至修改出一个更适合使用的版本提交上去 ,但发现这样做的工程量太大,需要花费的精力太多。我需要找到一个简单的,又容易实现的方案,达到我们上面的两点需求。因此,我选择了对MSBuild进行扩展,而MSBuild本事又是支持这种扩展的,这给我带来了很大的方便。

熟悉MSBuild配置文件的朋友一定知道里面有很多Task供我们使用,比如:CallTarget,Exec,MakeDir,VCBuild等等。同时,也提供机制让我们实现自己的自定义Task。详细使用可以参考微软的文档:How to write a Task

现在,我们可以实现一个自己的Task了,那么在我们自定义的这个Task里,我们应该做些什么呢?恩,再来整理一下思路:

1. 我们需要知道更新的代码属于哪个工程。

2. 我们需要知道编译该工程的同时,还需要编译哪些与之相关的工程。

首先解决第一个问题,如何知道更新的代码属于哪个工程?其实,一个更加实际的问题,如何知道更新了哪些代码? 我曾经尝试过使用CCNET一样的办法,调用svn --log对入库记录进行查询,然后每次保存好上次更新的状态,再判断这次更新相对于上次改动了哪些。做到这些其实非常容易,但是,存在一个问题,CCNET本身也有一个机制在记录着SVN更新的状态(state文件),如果我又记录一个自己的SVN更新历史的文件,可能和CCNET本身记录的有时间差,使得整个流程下来对于要更新的和编译的代码文件变得非常不确定。因此,我最后打算直接使用CCNET获取到的文件更新列表。要获取CCNET获取的SVN更新列表,只需要在CCNET的配置文件中加入下面一段:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><prebuild>     <modificationWriter>         <filename>mods.xml</filename>         <path>x:\ccnet\svn\build</path>     </modificationWriter> </prebuild>

这样,每当CCNET更新SVN代码时,都会将SVN的更新记录到mods.xml中,mods.xml的格式大致如下:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><?xml version="1.0" encoding="utf-8"?> <ArrayOfModification xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">     <Modification>         <Type>Modified</Type>         <FileName>xxx.cs</FileName>         <FolderName>/trunk/ProjectA/</FolderName>         <ModifiedTime>2009-04-05T16:09:58.545196+08:00</ModifiedTime>         <UserName>coderzh</UserName>         <ChangeNumber>8888</ChangeNumber>         <Version />         <Comment>Upload My Greate Code</Comment>     </Modification> </ArrayOfModification>

回到正题,通过读取mods.xml知道CCNET此次编译前更新的代码后,如何判断改代码文件属于哪个工程呢?很容易想到的就是通过路径判断,比如上面的代码的FolderName是/trunk/ProjectA,我们就能断定该代码文件属于ProjectA。当然,我们还需要一个配置文件,用于说明哪些目录下的代码属于哪个工程,即代码文件与工程的对应关系。这些信息我们可以直接在MSBuild的配置文件中设置:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><PropertyGroup>     <FrameworkAPath>\trunk\Framework</FrameworkAPath>     <ProjectA>\trunk\ProjectA</ProjectA>     <ProjectB>\trunk\ProjectB</ProjectB>     <ProjectC>\trunk\ProjectC</ProjectC> </PropertyGroup> <ItemGroup>     <SvnFolder Include="$(FrameworkAPath);">         <ProjectName>FrameworkA</ProjectName>     </SvnFolder>     <SvnFolder Include="$(ProjectAPath);">         <ProjectName>ProjectA</ProjectName>     </SvnFolder>     <SvnFolder Include="$(ProjectBPath);">         <ProjectName>ProjectB</ProjectName>     </SvnFolder>     <SvnFolder Include="$(ProjectCPath">         <ProjectName>ProjectC</ProjectName>     </SvnFolder> </ItemGroup>

OK,我们的第一个问题解决了,接下来的问题是,如何设置工程间的这种关联关系。同样的,我们通过MSBuild配置文件中的Target来设置,我们看下面的配置就会明白了:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><Target Name="FrameworkA">     <MSBuild Projects="$(FrameworkAPath)\FrameworkA.sln" Properties="Configuration=Release"/>     <CallTarget Targets="ProjectA" />         <CallTarget Targets="ProjectB" />     <CallTarget Targets="ProjectC" /> </Target> <Target Name="ProjectA">     <MSBuild Projects="$(ProjectAPath)\ProjectA.sln" Properties="Configuration=Release"/> </Target> <Target Name="ProjectB">     <MSBuild Projects="$(ProjectBPath)\ProjectB.sln" Properties="Configuration=Release"/> </Target> <Target Name="ProjectC">     <MSBuild Projects="$(ProjectCPath)\ProjectC.sln" Properties="Configuration=Release"/> </Target>

我们看到,我们通过Target的设置成功的将不同工程联系了起来,当我们需要编译FrameworkA时,我们只需要调用FrameworkA这个Target,它会先FrameworkA编译,然后再调用ProjectA,ProjectB,ProjectC的编译。

哈哈,一切准备工作都就绪了,我们需要在MSBuild的扩展Task里完成的任务就是:

1. 读取mods.xml,自动判断入库代码所属工程。

2. 返回需要编译的工程名列表。

我们在VS里建立一个DLL工程,然后添加Microsoft.Build.Utilities和Microsoft.Build.Framework的引用,然后编写我们自定义的Task类,我取名为MyTask,让它继承Task类,我们要做的是重写其中的Execute方法。MSBuild具体的Task写法请参照How to write a Task,我这里不再重复了,下面是的MyTask代码:

MyTask

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Build.Utilities; using System.Xml; using System.Collections; using Microsoft.Build.Framework; using System.IO; namespace CoderZh.MyTask {     public class MyTask : Task     {         [Output]         public ITaskItem[] Targets { get; set; }         [Required]         public ITaskItem[] Projects { get; set; }         [Required]         public string SvnModifyFile { get; set; }         [Required]         public string StateFile { get; set; }         private DateTime curBuildTime;         private DateTime lastBuildTime;         private Boolean lastBuildResult = false;         /**//// <summary>         /// My Task Run From Here         /// </summary>         /// <returns></returns>         public override bool Execute()         {             if ((this.Projects == null) || (this.Projects.Length == 0))             {                 return true;             }             //Read last build time and result             this.ReadLastBuildStatus();             if (!this.lastBuildResult || this.lastBuildTime.Day != DateTime.Now.Day)             {//If last build fail, or it is another day, then run all the targets                 Log.LogMessage("Last build fail, or it is another day, then run all the targets");                 this.SetAllTargetsToRun();             }             else             {//check the svn and run the specify targets                 this.SetTargetsToRunBySvnModify();             }             return true;         }         /**//// <summary>         /// Read Last Build Result, Success Or Not         /// </summary>         private void ReadLastBuildStatus()         {             try             {                 XmlDocument doc = new XmlDocument();                 doc.Load(this.StateFile);                 XmlNode lastBuildTimeNode = doc.SelectSingleNode("/IntegrationResult/StartTime");                 this.lastBuildTime = Convert.ToDateTime(lastBuildTimeNode.InnerText);                 XmlNode lastBuildResultNode = doc.SelectSingleNode("/IntegrationResult/LastIntegrationStatus");                 this.lastBuildResult = lastBuildResultNode.InnerText.ToLower() == "success";                 Log.LogMessage("Load from : {0}\r\nLastBuild Time : {1}\r\nLastBuild Result : {2}",                                this.StateFile, this.lastBuildTime.ToString(), this.lastBuildResult.ToString());                 doc = null;             }             catch(Exception ex)             {                 Log.LogWarningFromException(ex);                 this.lastBuildTime = DateTime.Today.AddDays(-1.0);                 this.lastBuildResult = false;             }         }         /**//// <summary>         /// Set All targets to run         /// </summary>         private void SetAllTargetsToRun()         {             ArrayList list = new ArrayList();             foreach (ITaskItem item in this.Projects)             {                 string targetName = item.GetMetadata("ProjectName");                 if (!list.Contains(targetName))                 {                     list.Add(targetName);                 }             }             ArrayList targetList = new ArrayList();             foreach (string item in list)             {                 targetList.Add(new TaskItem(item));             }             this.Targets = (ITaskItem[])targetList.ToArray(typeof(ITaskItem));         }         /**//// <summary>         /// Set Targets to run by SVN Modify         /// </summary>         private void SetTargetsToRunBySvnModify()         {             this.curBuildTime = DateTime.Now;             ArrayList list = new ArrayList();             List<string> mods = GetModification();             foreach (ITaskItem item in this.Projects)             {                 string projectFolder = Path.GetFullPath(item.ItemSpec);                 string excludeFolder = item.GetMetadata("Exclude");                 excludeFolder = String.IsNullOrEmpty(excludeFolder) ? String.Empty : Path.GetFullPath(excludeFolder);                 Log.LogMessage("\nprojectFolder:" + projectFolder);                 foreach (string mod in mods)                 {                     string modifyFolder = Path.GetFullPath(mod.Replace(@"/trunk", ".."));                     Log.LogMessage("\t-- modifyFolder:" + modifyFolder);                     if (modifyFolder.Contains(projectFolder))                     {                                                  if (!String.IsNullOrEmpty(excludeFolder) && modifyFolder.Contains(excludeFolder))                         {                             Log.LogMessage("Exclude : {0}", excludeFolder);                             continue;                         }                         string targetName = item.GetMetadata("ProjectName");                                                  Log.LogMessage("Matched : {0}", targetName);                         list.Add(new TaskItem(targetName));                         break;                     }                 }             }             this.Targets = (ITaskItem[])list.ToArray(typeof(ITaskItem));         }         /**//// <summary>         /// Get Modification From mods.xml         /// </summary>         /// <returns></returns>         private List<string> GetModification()         {             List<string> modList = new List<string>();             try             {                 XmlDocument doc = new XmlDocument();                 doc.Load(this.SvnModifyFile);                 XmlNodeList modNodeList = doc.SelectNodes("/ArrayOfModification/Modification");                 foreach (XmlNode modNode in modNodeList)                 {                     XmlNode folderNode = modNode.SelectSingleNode("FolderName");                     modList.Add(folderNode.InnerText);                 }                 doc = null;             }             catch (Exception ex)             {                 Log.LogWarningFromException(ex);             }             return modList;         }     } }

接下来完成最后一步,配置完成我们的MSBuild配置文件。我们添加MyTask相关的内容:

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--><UsingTask AssemblyFile="CoderZh.MyTask.dll" TaskName="MyTask"/> <Target Name="Build">     <MyTask SvnModifyFile="$(SvnModifyFile)" StateFile="$(CCNetStateFile)" Projects="@(SvnFolder)">         <Output TaskParameter="Targets" ItemName="TargetNames" />     </MyTask>     <Message Text="Targets to be call:@(TargetNames)"/>     <CallTarget Targets="@(TargetNames)" /> </Target>

OK,搞定!

三、总结

通过上面的方法,我们实现了:

1.CCNET只更新需要编译的工程代码,大大减少了SVN更新的时间,同时,也减少了SVN编译的次数。

2.我们实现了只编译入库代码所属工程,以及其相关联的工程。大大减少了编译工程的范围,缩短了编译时间。

我也知道,上面的解决方案不够完美,也许有更加直接,简单的处理办法,也请大家拿出来讨论讨论,不甚感激。

本文相关的配置文件及代码如下,希望对大家有微薄之助。

代码:/Files/coderzh/mytask/MyBuild.rar

MSBuild 配置文件:/Files/coderzh/mytask/mybuild.txt

CCNET配置文件:/Files/coderzh/mytask/ccnet.txt

本文转载自:http://www.cnblogs.com/coderzh/archive/2009/04/05/1429858.html

时间: 2024-09-29 08:35:35

CCNET+MSBuild+SVN实时构建的优化总结的相关文章

Mac下Jenkins+SVN+Xcode构建持续

1 安装Jenkins Jenkins是基于Java开发的一种持续集成工具.所以呢,要使用Jenkins必须使用先安装JDK. JDK安装 JDK 下载地址 jdk 1.8.png 安装JDK的过程略,别说你不会安装(如有不会安装的,自行百度). Jenkins安装 Jenkins 下载地址 Jenkins安装文件.png 点击图中 Mac OS X,会自动下载[jenkins-1.644.pkg]安装过程略(双击jenkins-1.644.pkg后,下一步就OK了). 注意: 1.Jenkin

ROS 实时构建八叉树模型

ROS 实时构建八叉树模型 ROS 毕业设计的主要内容是室内环境的建模,并转换成八叉树模型,以供后续使用之需.这里介绍离线八叉树模型建立和实时的八叉树环境模型构建. 1. 环境搭建 平台: ubuntu下ROS,用过hydro和indigo,其他版本有待确认(ros的安装请参考ros wiki): 开源室内建模项目,ROS下了解的RGBD开源项目有RGBD_SLAM和rtabmap,这里使用的是rtabmap,rtabmap的安装请参考ros rtabmap. ros下的octomap包,安装$

Docker镜像构建的优化总结

Docker镜像构建的优化总结 随着我们对docker镜像的持续使用,在此过程中如果不加以注意并且优化,镜像的体积会越来越多.很多时候我们在使用docker部署应用时,会发现镜像的体积至少有1G以上.镜像体积的增大,不单单会增加磁盘资源与网络资源的开销,也会影响应用的部署效率,使得应用的部署时间会越来越长.因此我们需要减少部署镜像的体积以加快部署效率,降低资源的开销.而对于镜像的优化,可以通过对dockerfile的优化来实现. 一.镜像最小化 1.选择最精简的基础镜像 选择体积最小的基础镜像可

(持续集成)win7上部署Jenkins+MSBuild+Svn+SonarQube+SonarQube Scanner for MSBuild (一)

一.Jenkins介绍 jenkins是一个广泛用于持续构建的可视化web工具,持续构建说得更直白点,就是各种项目的”自动化”编译.打包.分发部署.jenkins可以很好的支持各种语言(比如:java, c#, php等)的项目构建,也完全兼容ant.maven.gradle等多种第三方构建工具,同时跟svn.git能无缝集成,也支持直接与知名源代码托管网站,比如github.bitbucket直接集成. jenkins官网地址为https://jenkins.io/index.html,jen

Jenkins配置MSBuild实现自动部署(MSBuild+SVN/Subversion+FTP+BAT)

所要用到的主要插件: [MSBuild Plugin] 具体操作: 1.配置MSBuild的版本 [系统管理]->[Global Tool Configuration]->[MSBuild],点击[新增MSBuild]进行版本的添加,如下: 注意:其中Path to MSBuild为文件夹路径,代码要编译的那台机器的文件夹,如果绑定的是slave时,这个路径就代表这slave的路径 最后,点击[Save]完成保存. 2.新建项目进行测试 这里假设在要进行构建的机器上已经新建好项目,并放在“D:

(持续集成)win7上部署Jenkins+MSBuild+Svn+SonarQube+SonarQube Scanner for MSBuild (第二发)

这一篇进入实战,走起.... 登录jenkins,如下图 点击上图中的“新建”按钮,进入下图 输入项目名称,选择“构建一个自由风格的软件项目”即可,点击“ok”,跳转到下图 svn源代码管理(选择代码管理器中的subversion) 构建触发器信息维护(什么时候down源代码到本地) 第一个的意思是远程触发构建,就是访问url的方式触发构建 第二个的意思是另一个项目构建完成后,进行构建 第三个的意思是定时去构建(不论源代码是否有新的commit) 第四个略过 第五个定期去构建(有新的commit

SVN + Jinkins 构建自动部署

1. 前言 因为研发部门不想把他们的源代码 git 到服务器再编译,git + maven + jenkins 的方式行不通,于是采用 svn + jenkins的方式,流程如下: 只需要 程序员 手动提交到svn ,后面的事件都是由 jenkins 自动完成的. 2. 实现过程 环境介绍 2.1 svn 服务器搭建 时间同步.selinux .iptables 这些初始化工作就不在说了. 安装svn程序包 [[email protected] ~]#yum install subversion

构建和优化深度学习模型(神经网络机器识图)

DSL(Deep Learning Service)是基于华为云强大高性能计算提供一站式深度学习平台服务,内置大量优化的网络模型算法,以兼容.便携.高效的品质帮助用户轻松使用深度学习技术,通过灵活调度按需服务化方式提供模型训练.评估与预测. 深度学习训练模型:数据准备-->数据模型训练(优化参数)-->反馈损失值-->更多训练 评估模型:返回结果为预测值与测试数据集标注值的错误率或准确率 深度学习模型的优化方法? 训练次数的调整? 代价函数的调整? 学习率的调整? 训练算法的优化? 神经

webpack构建速度优化-记录一次公司项目build优化(不完全记录)仅供翻阅参考

打包速度 转化AST—>遍历树—>转化回代码(具体语法配置参考webpack,这边只是个人的一些过程记录,并不包含详细过程)1.缓存 cache-loader2.多核 happypack threads3.抽离 DllPlugin.externals4.拆分 Docker 优化项目:测量插件speed-measure-webpack-plugin 用法: 优化前:可以看到我们公司的项目还是比较残暴的!!开启AOT(Angular5项目)之后,线上构建达到了41分钟! 测量出现的问题:raw-l