如何编写实用程序 msbuild 项目以使其依赖于 "real" C# 项目?

How to code a utility msbuild project so that it depends on a "real" C# project?

我所说的实用程序是指没有任何 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:\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]>

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

注意,如果我 运行 构建单线程输出将是 *** 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 - 最好。

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

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

提供

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

第一次尝试:Build 做了一个 lot 所以我想利用它只是为了你需要的东西,并保持它完好无损,其余的应该这样做。不是很干净,但可以:

<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 工作,因此接近 'true' 实用程序项目(将其命名为 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>