对小的, 或者中型的solution, 或者比较规矩的.NET solution, 我是指没有混合了 native C++/managed C++项目的情况, 你可以用solution 任务, 可以用project 任务, nant 的build 文件会保持相对较小, 本质上也跟从命令行调用 devenv 没有太大区别. 这样, 你会觉得貌似nant 很好用, 你很顺利.
一旦碰到大的solution, 混合了C/C++, 混合了native和managed的代码, 你的solution任务几乎可以肯定是通不过的, 当然, project任务也一样. 此时还是需要手工把 solution文件转换成nant文件, 每个项目对应一个target, 这绝不是个好干的差事, 我以前几次尝试都因为这个中止, 好在这次, 花了大力气搞定.
先介绍一下要转换的solution, .sln文件4070行, 项目共456个, 其中C++项目37个, 有native的, 也有managed c++的, 其余为C#项目.
当然, 我知道slingshot, 也试过用xslt的方案来转换, 几乎查找了google所能提供的一切, 没有现成的省事的办法可以对付这种项目, slingshot说的明白, 现在就是不支持managed c++这种东西. xslt更是过于简单, 放到真实世界中的项目时, 这些东西立马歇菜了, 我对slingshot/xslt这样的工具是失望的, 其开发者没有把它推进到一个真正实用的程度, 而是停留在仅仅对一些情况有用的少数场合.
一开始希望用slingshot来转换所有的C#项目, 毕竟这是大头, 但是因为.sln中混合了C++项目, 它在碰到这样的项目时失败之后就不能继续向下走. 于是先手工移除所有的C++项目, 再用slingshot来转换, 经历了一些由于.sln文件/.csproj不那么规范引起的错误, 以及slingshot解释.sln文件时假设项目的目录名不能含有空格的问题之后(幸亏有源代码, 需要修改它的一个正则表达式, sourceforge上的邮件列表里也已经有人提过这个问题), 终于得到了一个50000行左右的.build文件. 这个文件实现很丑陋, C#原封不动地把所有的文件列出来, 这么多行的.build文件绝大部分都是这种文件名的罗列.
我希望有一种干净的, 能跟.csproj项目文件保持同步的, 结构来维护所有的C#项目, 一开始只是通过不同的属性值让一些相似的项目来共享一些"变量", 后来发现可以有一种通用的方法处理至少是我的.sln文件里的所有C#文件. nant提供的灵活性勉强可以实现, 说勉强是因为还要借助于接下来要介绍的额外小工具.
VS.NET中IDE的华丽外表下, 你build项目时它干了什么? 不是那么简单, 你也许看不到csc.exe/al.exe进程在任务管理器中显示出来, 到现在为止我也没找到资料说怎么通过命令行完整地克隆IDE 的build过程所做的事, (我讨厌它对依赖关系的处理, 准确说是错误的处理). 以前曾经试图用命令行编译出一个satellite assembly时, 会出现运行时资源找不到的错误. 扎到assembly内部一下资源才发现是资源名称的问题, .NET 的IDE编译时你的资源的名字跟以下几个因素有关:
* .resx 文件所 depend on的那个.cs文件中的第一个类, 注意是在源代码中第一个出现的类, 把你的UI代码相关的类放到后面会让你遭遇意外!
* 项目的默认名称空间
* 它还后辍了一个 .resources, 它还会对目录/文件名中的特殊字符做些替换, 比如把$替换成_, 具体替换的规则我也无从查起.
这些阴暗险恶的角落都使得用命令行来生成IDE一样的结果有了很大挑战.
编译所有C#项目的通用target在附件里, 410 行, 不能贴在这里, 注意不能作为独立的.build文件运行, 可以copy到现有的.build文件中编译你的C#项目. 下面简单说下实现的原理:
1. 所有csc编译选项, 要编译的源文件, .resx资源文件, 其它资源文件 都从.csproj文件中动态地抽取出来.
虽然nant提供了xmlpeek这样的任务来做XML的操作, 但它是每运行一次就打开一次XML文件, 灵活性和效率都达不到要求, 我写了一个小工具, 用于从.csproj生成简单的文本格式的输出, 通过nant可以对这个简单的文本格式输出进行解析, 产生出等价的csc/al/resgen命令行.
2. 每个项目下额外产生两个文件:
2.1 XXX.nant.list, 其内容大致如下:
OutputType=Library
Language=es,ja,ko,pt,zh-chs,zh-cht
CS=AssemblyInfo.cs
CS=Colors.cs
Resx=|Colors.resx
AllowUnsafeBlocks=true
CheckForOverflowUnderflow=false
DebugSymbols=true
DefineConstants=DEBUG;TRACE;NO_TESTPERFORMANCE;SECURITY
WarningLevel=4
AssemblyName=Common
其中 CS=和Resx=开头的项允许多个, 分别代表要编译的源文件和资源文件, 其中Resx=项用|隔开, 前一部分是资源编译后的正确名称(不含.resources后辍), 后一部分是资源的不带locale部分的名称, 如file.zh-chs.resx, 只取file部分, 至于具体有哪些locale对应的资源文件, 在nant编译的过程中动态控制.
其中 Language=es,ja,ko,pt,zh-chs,zh-cht是一个语言项列表, 来自对file.zh-chs.resx中的文件名解析, 这是所有.resx所涉及资源的一个并集, 比如 file.resx有对应的zh-chs/zh-cht资源, 而另一个file2.resx有对应的ja,es资源, 则把所有这些语言都列出来, 尽管file.resx没有对应的ja,es资源, 这些可以通过nant进行动态的判断.
其它的项基本上是从.csproj原封不动地取出来的属性值.
* 目前CS=源文件的抽取还没有考虑link 到其它目录的源文件的情况
2.2 XXX.nant.cs.list
这是一个C#源文件的列表, csc编译器只输入这些文件, 为了"缓存"对XXX.nant.list文件的解析结果而设.
一行一个文件. 这个文件可以在下面的csc任务中被整个引用:
<sources>
<includesfile name="${csharp_list}"/> </sources>
|
其中属性变量 csharp_list就赋值为 XXX.nant.cs.list
3. csc编译选项的处理
<csc unsafe="${AllowUnsafeBlocks}" checked="${CheckForOverflowUnderflow}" target="${OutputType}"
output="${output_file}"
debug="${DebugSymbols}" define="${DefineConstants}" warninglevel="${WarningLevel}" verbose="true">
|
可以看到这些属性变量名几乎跟 2.1 中 XXX.nant.list文件的起始项一模一样. 当然, 也跟.csproj中的属性名一样, 这样可以减少记忆的负担. 这部分是可以直接从.csproj迁移过来的命令行选项.
4. 引用的处理
真实的C#项目一般都需要引用其它项目, 如何处理整个solution中的引用, 而不是为单个项目生成自己的引用列表?
<references>
<include name="${dot_net11_dll_dir}\System.dll" />
<include name="${dot_net11_dll_dir}\System.Data.dll" />
<include name="${dot_net11_dll_dir}\System.Drawing.dll" />
<include name="${dot_net11_dll_dir}\System.Windows.Forms.dll" />
<include name="${dot_net11_dll_dir}\System.Xml.dll" />
<include name="${build.dir}\ICSharpCode.SharpZipLib.dll"
if="${string::contains(depends, ',Updater.ExtraRef,')}"/>
/>
<!-- The following reference list is generated from solution/csproj files automatically (103 files)-->
<include name="${build.dir}\main.exe"
if="${string::contains(depends, ', main ,')}"/>
<include name="${build.dir}\proj1.dll"
if="${string::contains(depends, ', proj1 ,')}"/>
|
注释
阅读(1524) | 评论(0) | 转发(0) |