CSBrick:将您的项目作为源文件包含,而不是二进制文件






4.92/5 (6投票s)
轻松地在源级别重用整个项目的源,而不是在二进制级别
引言
.NET 提供了许多很酷的功能,可以帮助您以期望的方式部署应用程序。但不幸的是,静态链接(嵌入依赖程序集代码的功能)并不包含在其中。
CSBrick 是解决此问题的一种变通方法。它的作用是收集特定项目指向的所有源,然后将其合并并最小化到一个“源块”中——一个单一的 C# 文件,您可以轻松地将其包含到您的项目中,而不是引用等效的二进制类库。多余的空格和注释会被剥离,因为这个文件并非供人类阅读——而是供原始文件阅读。全局 using
和 #define
会被移到文件顶部,重复的 using
会被移除。文档注释会得到保留。
这段代码是我 Build Pack 的一部分——一套构建工具和代码,旨在使构建构建工具更容易、更好。构建工具不能携带额外的 DLL,因为这会使它们作为预构建步骤变得复杂,因此将所有代码保留在单个可执行文件中很重要。此工具非常适合这一点。
构建这个大杂烩
构建包使用自身来构建自身,但由于我从提供的 zip 文件中删除了所有二进制文件,因此您需要自行启动。在 Visual Studio 中打开它,并在 **Release** 模式下构建几次,直到文件锁定错误消失。这是 VS 在处理循环构建步骤时出现的卡顿,虽然没问题,但会导致这些消息出现。第二次构建后,您应该就没问题了,但请切换到“输出”窗格以确保构建成功,因为“错误”窗格倾向于“卡住”在此问题上。最后,切换到 Debug 模式,您就可以开始工作了。任何时候需要重新构建,您都必须在 Release 模式下进行重新构建,才能将对构建工具本身的更改反映到下一次构建中。这是因为这些项目使用同一解决方案中其他项目的 Release 二进制文件作为构建步骤来构建自身。
使用这个烂摊子
使用起来非常直接。这是使用界面
CsBrick merges and minifies C# project source files
csbrick.exe <inputfile> [/output <outputfile>]
[/ifstale] [/exclude [<exclude1> <exclude2> ... <excludeN>]]
[/linewidth <linewidth>] [/definefiles]
<inputfile> The input project file to use.
<outputfile> The output file to use - default stdout.
<ifstale> Do not generate unless <outputfile> is older than <inputfile>
or its associated files.
<exclude> Exclude the specified file(s) from the output.
- defaults to "Properties\AssemblyInfo.cs"
<linewidth> After the width is hit, break the line at the next opportunity
- defaults to 150
<definefiles> Insert #define decls for every file included
<inputfile>
必须是 .csproj 文件。它适用于 Visual Studio 2017 项目,但也应该适用于其他项目。我只是还没有在其他版本上尝试过。
您可以将其添加为 Visual Studio 中项目的预构建事件。只需转到“项目 | 属性 | 生成事件”,然后添加命令行。我建议使用 $(SolutionDir)
和 $(ProjectDir)
等宏。例如,包含的 Deslang 项目具有此生成事件
$(SolutionDir)CSBrick\bin\Release\csbrick.exe
$(SolutionDir)CodeDomGoKit\CodeDomGoKit.csproj /output $(ProjectDir)CodeDomGoKit.brick.cs
在这种情况下,它会获取 CodeDomGoKit.csproj 的所有代码,并将其打包成一个单一文件 CodeDomGoKit.brick.cs,保存在其自己的项目目录中,然后将其包含。这基本上相当于在“引用”中添加 CodeDomGoKit,但没有额外的程序集,而这正是关键所在。
不要编辑 brick 文件。修改创建 brick 文件的原始源。由于生成事件,任何时候发生更改,它都会自动重新生成。
编写这个混乱的程序
我之前已经编写过一个早期的版本,但这个版本更适合我的需求。它使用了我的 ParseContext
类,我在 这里 进行了介绍。它实际上并没有解析 C#,而是对 C# 进行基本的标记化,并丢弃多余的标记,如空格和(非文档)注释。唯一真正棘手的地方是知道何时丢弃空格,以及跳过字符串内部的内容。
这一切都在 Minifier.cs 中,主要是 MergeMinifyBody()
。大部分代码是一个大型的 switch case,执行类似这样的操作
case '\"':
isIdentOrNum = false;
pc.TryReadCSharpString();
writer.Write(pc.GetCapture());
ocol += pc.CaptureBuffer.Length;
break;
case '\'':
isIdentOrNum = false;
pc.TryReadCSharpChar();
writer.Write(pc.GetCapture());
ocol += pc.CaptureBuffer.Length;
break;
每次我们设置 isIdentOrNum
时,下一个标记前面都会加上一个空格,否则不会发出任何空格。ocol
跟踪我们的输出列,以便在超过行宽(建议 150)时换行——这可以防止编辑器因单行过长而卡住。
使用 Minifier.cs 很简单,只需调用此方法
void MergeMinify(TextWriter writer, int lineWidth = 0,
bool defineFiles=false,params string[] sourcePaths)
例如
Minifier.MergeMinify(output, linewidth, definefiles,
inputs.ToArray());
其中 output
是您的输出 TextReader
,linewidth
是所需的行宽(建议 150),definefiles
是一个布尔值,如果为 true
,则为每个包含的文件插入 #define
,例如 #define FOO_CS
表示“foo.cs”,inputs 是一个 string
文件名数组,用于处理。
最后,您得到的大部分杂乱的代码就是项目中(除了 AssemblyInfo.cs)的所有代码打包到一个文件中。使用它而不是引用项目。这会增加二进制文件的大小,但可以避免您需要携带 DLL。这就是为什么我说它是缺乏静态链接的一种变通方法。
历史
- 2019 年 12 月 16 日 - 初次提交