【问题标题】:How to code a utility msbuild project so that it depends on a "real" C# project?如何编写实用程序 msbuild 项目,使其依赖于“真正的”C# 项目?
【发布时间】:2022-01-08 15:02:04
【问题描述】:

我所说的实用程序是指没有任何 C# 文件、不生成 .NET 程序集但实现一些自定义构建逻辑的项目。

我本可以将其安排为感兴趣的 C# 项目中的 AfterBuild 目标,但我不想增加该 C# 项目的构建时间。相反,我希望 msbuild 与该 C# 项目的其他依赖项并行运行此逻辑。

一种解决方案是创建一个虚拟 C# 项目,该项目将真正构建一些虚拟代码并将我的逻辑放在 AfterBuild 目标中。但这很丑。

所以,这是我的解决方案(剧透警告 - 它不起作用):

目录结构

C:\work\u [master]> tree /F
Folder PATH listing for volume OSDisk
Volume serial number is F6C4-7BEF
C:.
│   .gitignore
│   Deployer.sln
│
├───Deployer
│       Deployer.csproj
│
├───DeploymentEngine
│       DeploymentEngine.csproj
│
└───Utility
        Utility.csproj

C:\work\u [master]>

Deployer.csproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
    <ProjectGuid>{B451936B-54B7-41D1-A359-4B06865248CE}</ProjectGuid>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <OutputType>Library</OutputType>
    <BaseOutputPath>bin</BaseOutputPath>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj">
      <Project>{901487BE-C604-4251-8485-3E96D5993145}</Project>
      <Name>DeploymentEngine</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="TakeTime" AfterTargets="Build">
    <Exec Command="powershell -NoProfile -Command Start-Sleep -Seconds 5" />
  </Target>
</Project>

是的,这是一个遗留风格的项目,因为真正的解决方案是遗留和 SDK 风格项目的混合。

DeploymentEngine.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
  <Target Name="TakeTime" AfterTargets="Build">
    <Exec Command="powershell -NoProfile -Command Start-Sleep -Seconds 5" />
  </Target>
</Project>

Utility.csproj

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
    <EnableDefaultItems>False</EnableDefaultItems>
    <GenerateAssemblyInfo>False</GenerateAssemblyInfo>
  </PropertyGroup>
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
  <Target Name="Build">
    <Message Text="*** Good" Importance="high" Condition="Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
    <Message Text="*** Bad" Importance="high" Condition="!Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
  </Target>
  <Target Name="Clean" />
  <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
  <ItemGroup>
    <ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj" />
  </ItemGroup>
</Project>

Deployer.sln

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deployer", "Deployer\Deployer.csproj", "{B451936B-54B7-41D1-A359-4B06865248CE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeploymentEngine", "DeploymentEngine\DeploymentEngine.csproj", "{901487BE-C604-4251-8485-3E96D5993145}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utility", "Utility\Utility.csproj", "{9369D18D-D81D-4CA3-A287-C62C89BFB751}"
EndProject
Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
                Release|Any CPU = Release|Any CPU
        EndGlobalSection
        GlobalSection(ProjectConfigurationPlatforms) = postSolution
                {B451936B-54B7-41D1-A359-4B06865248CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {B451936B-54B7-41D1-A359-4B06865248CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {B451936B-54B7-41D1-A359-4B06865248CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {B451936B-54B7-41D1-A359-4B06865248CE}.Release|Any CPU.Build.0 = Release|Any CPU
                {901487BE-C604-4251-8485-3E96D5993145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {901487BE-C604-4251-8485-3E96D5993145}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {901487BE-C604-4251-8485-3E96D5993145}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {901487BE-C604-4251-8485-3E96D5993145}.Release|Any CPU.Build.0 = Release|Any CPU
                {9369D18D-D81D-4CA3-A287-C62C89BFB751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {9369D18D-D81D-4CA3-A287-C62C89BFB751}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {9369D18D-D81D-4CA3-A287-C62C89BFB751}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {9369D18D-D81D-4CA3-A287-C62C89BFB751}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {A70FF6AB-85B1-49F0-B2B0-25E20256A88F}
        EndGlobalSection
EndGlobal

注意事项:

  • 我对两个“真正的”C# 项目进行了人为延迟。
  • 实用程序项目在其声明的依赖项之后运行时输出*** Bad,即不是在部署引擎项目之后。

现在让我们运行它:

C:\work\u [master]> git clean -qdfx ; msbuild /v:m /restore /m
Microsoft (R) Build Engine version 16.11.0+0538acc04 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored C:\work\u\DeploymentEngine\DeploymentEngine.csproj (in 171 ms).
  Restored C:\work\u\Utility\Utility.csproj (in 172 ms).
  *** Bad
  DeploymentEngine -> C:\work\u\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll
CSC : warning CS2008: No source files specified. [C:\work\u\Deployer\Deployer.csproj]
  Deployer -> C:\work\u\Deployer\bin\Debug\Deployer.dll
C:\work\u [master]>

输出表明 Utility 项目是首先构建的,尽管声明的意图是依赖于 DeploymentEngine 项目。

注意,如果我运行构建单线程输出将是*** Good,所以输出逻辑确实可以正常工作:

C:\work\u [master]> git clean -qdfx ; msbuild /v:m /restore
Microsoft (R) Build Engine version 16.11.0+0538acc04 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored C:\work\u\Utility\Utility.csproj (in 172 ms).
  Restored C:\work\u\DeploymentEngine\DeploymentEngine.csproj (in 172 ms).
  DeploymentEngine -> C:\work\u\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll
CSC : warning CS2008: No source files specified. [C:\work\u\Deployer\Deployer.csproj]
  Deployer -> C:\work\u\Deployer\bin\Debug\Deployer.dll
  *** Good
C:\work\u [master]>

所以仅仅声明ProjectReference 是不够的。似乎我应该实现某种目标以使其工作。

那么我错过了什么?我应该添加什么来让 msbuild 知道 Utility 项目必须在 DeploymentEngine 之后构建?

编辑 1

我知道我可以在解决方案文件中设置依赖项。但是,出于各种原因,我不想这样做。

编辑 2

我的最终目标是拥有一个在一个或多个“真实”C# 项目之后运行的基本实用程序项目。 IE。尽可能少的 .NET 构建导入。如果它可以有.proj 扩展,而不是.csproj - 最好。

【问题讨论】:

  • 你是否尝试在VS的解决方案级别设置项目依赖项(右键单击解决方案->项目依赖项)?
  • 我忘了说我不想这样做。让我更新帖子。
  • 您是否手动创建了解决方案?如果我将像您这样的项目添加到解决方案中,它似乎会自动找出 ProjectReference 表示依赖项并在项目依赖项中显示它。因此,事情以正确的构建顺序工作。此外,不允许删除该依赖项:“此依赖项是由项目系统添加的,无法删除”(另一方面,.sln 中也没有来自它的跟踪,所以似乎只是 ProjectReference 负责处理它)。 tldr;也不能在命令行上重现。提供最少的样本?
  • @stijn - 我已经用最少的复制更新了这个问题。非常感谢。
  • 好吧,我错过了前面的-m,确实引入了问题

标签: msbuild


【解决方案1】:

我想知道您为什么不将 &lt;Project Sdk="Microsoft.Net.Sdk"/&gt; 用于实用程序项目并阅读 the docs,因为它会在其他所有内容的末尾隐式导入 Sdk.targets,从而覆盖您的 Build 目标。

我还没有弄清楚究竟是怎么回事(现在没有更多时间,但我很确定应该有可能有一个更简单的项目并且仍然让 ProjectReference 正常运行 - 将是一个问题声明正确的属性和目标;这可能最终比仅仅在现有结构中进行更多的工作),但该目标是使 msbuild 尊重 ProjectReference 并保持正确的构建顺序的关键:除其他外,它依赖于 ResolveProjectReferences是负责实际处理 ProjectReference 的目标。 Msbuild 本身对这些一无所知,其逻辑由 Microsoft.Common.CurrentVersion.targets 提供。

因此,简单地覆盖 Build 目标将使 ProjectReference 被完全忽略。在不使用-m 时,该解决方案确实按所需顺序构建的唯一原因是实用程序项目排在最后。如果你在 .sln 中向上移动它,msbuild 会更早地构建它,它会打印 '*** Bad'。

第一次尝试:Build 做了很多很多,所以我认为仅将它用于您需要的东西,而其余部分则保持原样。不是超级干净,但可以:

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
    <GenerateAssemblyInfo>False</GenerateAssemblyInfo>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj">
    </ProjectReference>
  </ItemGroup>
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
  <!-- Override Compile instead of Build, thereby also skipping
        creating of Utility.dll -->
  <Target Name="Compile">
    <Message Text="*** Good" Importance="high" Condition="Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
    <Message Text="*** Bad" Importance="high" Condition="!Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
  </Target>
  <!-- Empty so it doesn't try to copy the nonexisting Utility.dll. -->
  <Target Name="CopyFilesToOutputDirectory" />
</Project>

第二次尝试:第一次尝试使用 Sdk.targets 等,它基本上是在说“我是一个完整的 .Net 项目”,因此是骇人听闻的解决方法。更简单的是仅使用 CurrentVersion.Targets 中的内容,即 ResolveProjectReferences 目标来使 ProjectReference 工作,因此接近“真正的”实用程序项目(命名为 Utility.proj):

<Project>
  <PropertyGroup>
    <!-- ProjectReference requires the referenced project to have the same version -->
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <!-- Required for Microsoft.Common.CurrentVersion.targets -->
    <OutputPath>bin</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\DeploymentEngine\DeploymentEngine.csproj">
    </ProjectReference>
  </ItemGroup>
  <!-- For ResolveProjectReferences and everything it does -->
  <Import Project="$(MSBuildBinPath)\Microsoft.Common.CurrentVersion.targets"/>
  <Target Name="Build" DependsOnTargets="ResolveProjectReferences">
    <Message Text="*** Good" Importance="high" Condition="Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
    <Warning Text="*** Bad" Condition="!Exists('..\DeploymentEngine\bin\Debug\net472\DeploymentEngine.dll')" />
  </Target>
</Project>

【讨论】:

  • 能否请您粘贴整个文件?
  • 当然;基本上只是用编译替换了构建并排除了清理/重建,因为这里不需要/相关。
  • @mark 还可以看到用更好的版本编辑
  • 有一个小问题,因为它不会阻止任何开发人员。但这有点难看——Visual Studio 拒绝打开 Utility 项目。它没有加载。我想这是一个小麻烦。我想这可能是一个新问题。
  • 对,没有检查。但是,即使 VS 想要的导入存在,它仍然可能会抱怨缺少其他东西,例如未设置配置/平台等。所以这是一堆没有功能的额外样板。但是是的,如果您需要它,可以单独提出一个问题。
猜你喜欢
  • 2015-01-14
  • 2014-05-05
  • 2020-07-24
  • 2016-01-13
  • 2020-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多