当使用MSBuild构建多个.csproj文件时,创建一个“内存中的.sln”

3
我们有一个代码项目,使用多个.sln文件,每个文件包含不同的项目(有些项目在不同的解决方案中共享,即在超过一个解决方案中存在)。
我们的问题是,使用项目引用时,实际项目必须也驻留在同一解决方案中。
所有这些项目都是在构建服务器上使用MSBuild进行构建的。
我想知道是否可能创建一个MSBuild脚本,以某种方式“导入”所有项目到单个MSBuild项目中,这样它将与所有项目实际位于同一Visual Studio .sln文件下一样?
例如,我希望有类似以下脚本的东西:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <!-- Builds all *.sln files under this repository. -->
    <ItemGroup>
        <SolutionFiles Include="**/*.sln" />
    </ItemGroup>

    <Target Name="Build">
        <MSBuild Projects="@(SolutionFiles)" Targets="Rebuild" />
    </Target>

</Project>

这是否意味着MSBuild将构建一个所有项目的内存中的“主项目”,从而克服了项目引用问题?

你如何运行MSBuild,是手动从命令行、脚本还是代码中? - VladL
从命令行运行它。 - lysergic-acid
5个回答

2
为什么不直接使用上面的语法来直接构建csprojs呢?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- Builds all *.*proj files under this repository. -->
<ItemGroup>
    <!--SolutionFiles Include="**/*.sln" /-->
    <ProjectFiles Include="**/*.*proj"/>

</ItemGroup>

<Target Name="Build">
    <MSBuild Projects="@(ProjectFiles)" Targets="Rebuild" />
</Target>

</Project>

即使在命令行模式下没有 .sln 文件,MSBuild 默认会构建项目引用。如果您想省略项目引用(例如按顺序逐个构建项目),只需使用 /p:BuildProjectReferences=false 参数。


我不确定我理解了你的建议。使用那个语法(包括PrjectFiles)会发生什么? - lysergic-acid
假设我包含所有项目文件(100个不同的.csproj文件),MSBuild是否会创建某种内部图形,知道先构建哪个以及引用哪个项目来构建? - lysergic-acid
我的上面的例子显然很基础,但对于大型源代码树,微软建议使用遍历项目文件(像你的)来管理构建包含。 - Nick Nieslanik
让我知道进展如何。我已经实现了很多基于遍历的构建系统,并取得了很大的成功。我很乐意回答更多问题。 - Nick Nieslanik
为了实现并发,msbuild使用级别机制来决定要构建哪些项目。 如果设置了BuildProjectReferences属性为True,则将获取所有必需的项目进行构建,包括那些上下文之外的项目。 然后,它将计算每个项目的最大依赖级别,即从该项目到任何其他参与项目的最长引用链的长度。 然后,它将按依赖级别分组构建项目,从最高级别开始到最低级别。 这个算法相当于循环查询“我现在能构建什么?”。 - Tamir Daniely
显示剩余3条评论

1
我曾经做过类似的事情,只是在同一个文件夹中有多个 .sln 文件,这些文件都引用了一些或所有的项目。在我的情况下,我为每种构建类型(CI、夜间、发布等)都有 .sln 文件。
例如,您可能总共有以下项目:
- 网站 - 网站单元测试 - 公共库 - 公共库单元测试 - 数据访问库 - 数据访问库单元测试 - 网站验收测试(例如 Selenium webdriver 测试) - 压力测试
您可以有以下解决方案:
1. CI 解决方案(提交时运行)。这将引用单元测试项目及其目标。 2. 每晚运行的夜间解决方案。这将引用压力和验收测试项目。 3. 发布时按需运行的发布解决方案。这将引用网站、公共和数据访问项目(即没有测试项目)。
这只是一个简单的例子,但希望能有所帮助。一旦设置了这个解决方案/项目结构,我不认为需要经常更改,但如果添加了新项目,则只需快速添加到相关的解决方案即可。

你是手动创建 .sln 文件的吗?这个能自动完成吗? - lysergic-acid
这些与编程有关的内容需要手动翻译,因为它们也被保存在源代码控制中。例如,CI解决方案包括测试项目,而发布解决方案则不包括。创建其他解决方案只需几分钟即可完成。 - MarkG
我不确定我完全理解你的建议。你能详细说明一下吗?如果我需要手动创建sln文件,如何保护自己免受每次添加到“主”解决方案文件中的新项目的更新影响? - lysergic-acid
谢谢。这仍然迫使我做两次工作(将项目添加到解决方案1,然后将其添加到包含所有项目的解决方案中)。我正在寻找一种动态构建包含所有 .csproj 的“主项目”的方法来进行构建。不确定是否可能。 - lysergic-acid

0

您可以编写一个小型控制台应用程序,它将查找所有解决方案文件并构建它们。它甚至会选择适当的MSBuild版本:

创建C#控制台应用程序

添加对Microsoft.Build的引用并使用以下代码

  static void Main(string[] args)
  {
     string[] files = Directory.GetFiles(args[0], "*.sln", SearchOption.AllDirectories);

     foreach (string path in files)
     {
        RunMsBuild(path);
     }
  }

  public static void RunMsBuild(string SrcDir)
  {
     ProjectCollection pc = new ProjectCollection();
     Dictionary<string, string> GlobalProperty = new Dictionary<string, string>();

     //GlobalProperty.Add("Configuration", "Debug");
     //GlobalProperty.Add("Platform", "x86");
     //GlobalProperty.Add("OutputPath", "c:\\src");

     BuildRequestData buidlRequest = new BuildRequestData(SrcDir, GlobalProperty, null, new string[] { "Build" }, null);
     BuildResult buildResult = BuildManager.DefaultBuildManager.Build(new BuildParameters(pc), buidlRequest);
     if (buildResult["Build"].ResultCode == TargetResultCode.Success)
     {
        Console.WriteLine("Build Success");
     }
     else
     {
        Console.WriteLine("Build Fail");
     }
  }

之后您可以从控制台或脚本运行它,并将搜索解决方案文件的路径作为参数传递。


0
我们需要在TFS(Azure DevOps)中合并几个.csproj文件,但排除其他文件以进行标准构建/部署。最终我得到了以下.proj文件。重要的事情是:
  • 目标使用<MSBuild ...而不是Microsoft示例中的<CSC
  • 我需要为每个构建操作(构建、重新构建、清理、还原)包括Targets="..."

最终我得到了一个看起来像这样的文件

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <ProjectReference Include="one.csproj" />
    <ProjectReference Include="two.csproj" />
    <ProjectReference Include="three.csproj" />
  </ItemGroup>
  <Target Name="Build">
    <MSBuild Projects="@(ProjectReference)" Targets="Build" />
  </Target>
  <Target Name="Clean">
    <MSBuild Projects="@(ProjectReference)" Targets="Clean" />
  </Target>
  <Target Name="Rebuild">
    <MSBuild Projects="@(ProjectReference)" Targets="Rebuild" />
  </Target>
  <Target Name="Restore">
    <MSBuild Projects="@(ProjectReference)" Targets="Restore" />
  </Target>
</Project>

0

算了,我错过了下面的答案,包括命令行模式的项目

这并不容易或美观。但是,如果您真的想走更加“全面”的构建自动化和管理路线,就像您所要求的那样,以下是开始变得更好的第4步。

1)在OP示例中使用MSBuild作为脚本:在您的示例中,这更像是自动化,就像使用shell或批处理文件一样,而不是真正获得任何MSBuild项目技巧。请参见#2有关.sln规则的说明,以及#3有关我认为您希望它更多地执行的内容,但实际上执行其他操作的说明。

2) 关于“仅内存” .sln 问题:基本上,我认为如果您希望 MSBuild 使用一个 .sln 文件来解决基于 .sln 的项目引用,则它不能处于“零影响”或“仅内存”状态,并且必须被刷新或保存到磁盘。即使您拥有项目系统本身,MSBuild 也会查看文件本身以解析引用,并期望它在文件系统中运行,因为它在单独的进程中运行,并具有自己的思想。因此,如果您想使用 .sln 文件,则必须有实际的 .sln 文件。因此,希望使用没有 .sln 文件残留的 .sln 方法是不可能的。如果您运行 VS 来运行 msbuild,它将生成这些文件并需要将它们刷新到磁盘中,然后它们将保持“零影响”状态直到构建...只是为了完全详细和清晰。

3) 关于“导入.proj”问题:我知道的与您目的类似的选项是<Import Project="my/proj/path/mine.csproj"/>,它就像批量包含文件一样,将这些文件视为外部项目的一部分并编译到该程序集中。微软内部经常使用此选项。以这种方式使用的导入忽略了导入的.proj文件中的大部分内容,并且基本上只是寻找它所包含的文件,就像在外部项目中包含的任何其他文件一样。因此,我认为这不会让您更进一步,除非您打算在同一个程序集中构建所有文件。如果您确实打算这样做,您将需要按照此意图构建所包含的.proj文件,并滥用/构建外部引用所需的结构等。

4) 这是引用定义。 关于引用和它们是否被复制、是否被假定放置在 GAC 中,或者“什么都不做,相信我,在运行时应用程序域搜索路径中会有它们”。有很多选项。你可以关闭大部分的这些选项,那么你只需要依赖于构建和语言编译器能够解析所引用程序集的类型和 whatnbot,以便编译你正在尝试编译的依赖程序集。如果你想一下,还有很多其他的引用,你不是自己构建的,构建会捡起并用来编译。所以真正的问题可能是,“如何让我的自动化构建处于一个状态,使我编译的依赖项表现得像任何其他引用?”

由于你控制构建过程,你可以控制 dll 的放置位置,并且可以负责构建顺序。而且...你可以控制引用。

这里有一个经典的问题:如果引用指向 .sln 项目,则在设计时您可以从外部程序集中查找类型,而无需构建。如果更改项目为不可知状态,则必须编译。您可以沿着许多方面进行一些巧妙的操作,包括条件性的.proj文件,这将加重开发人员的负担,并可能需要手动编辑以保持同步,或通过脚本来预先构建.proj文件过滤器以修复引用,或使它们成为非 .sln 引用并强制开发人员构建相依赖项以解决类型问题,在这种情况下,他们必须确保使用的环境与构建一致。


所以问题归结为引用是如何定义的,而且是的,任何MSBuild需要的垃圾都需要写入磁盘才能使MSBuild正常工作,与VS本身无关。你必须选择你的毒药,因为问题在于你不能同时两种方式定义引用。顺便说一下,这是一个经典的构建管理问题,你并不孤单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接