MSBuild:忽略不存在的目标

10

Solution1.sln包含两个项目:

  • ProjectA.csproj
  • ProjectB.csproj

ProjectB有一个名为“Foo”的自定义目标。我想运行:

msbuild Solution1.sln /t:Foo

这将失败,因为ProjectA没有定义“Foo”目标。

有没有办法使解决方案忽略缺少的目标?(例如,如果特定项目不存在该目标,则不执行任何操作)而不修改SLN或项目文件?

5个回答

10

如果您不想编辑解决方案或项目文件,并且希望它能够从MSBuild命令行而不是从Visual Studio中工作,则有两部分的解决方案。

首先,运行以下命令时出现的错误:

MSBuild Solution1.sln /t:Foo

并不是说ProjectA中没有Foo目标,而是整个解决方案中都没有Foo目标。正如@Jaykul所建议的那样,设置MSBuildEmitSolution环境变量将显示解决方案元项目中包含的默认目标。
以元项目为灵感,您可以在解决方案文件旁边引入一个名为“before.Solution1.sln.targets”的新文件(文件名模式很重要),其内容如下:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Foo">
    <MSBuild Projects="@(ProjectReference)" Targets="Foo" BuildInParallel="True" Properties="CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
  </Target>
</Project>

这里的MSBuild元素大部分只是从解决方案元项目的发布目标中复制而来。根据您的情况调整目标名称和其他细节。

有了这个文件,现在会出现ProjectA不包含Foo目标的错误。根据项目间的依赖关系,ProjectB可能会构建或不会构建。

因此,第二步,为了解决这个问题,我们需要为每个项目提供一个空的Foo目标,然后在实际已经包含该目标的项目中覆盖它。

我们通过引入另一个文件,例如"EmptyFoo.targets"(名称不重要),来实现这一点,其内容如下:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Foo" />
</Project>

然后我们让每个项目自动导入此目标文件,可以通过在运行MSBuild时添加额外的属性来实现,例如:

MSBuild Solution1.sln /t:Foo /p:CustomBeforeMicrosoftCommonTargets=c:\full_path_to\EmptyFoo.targets

请在第一个目标文件的MSBuild元素的属性中包含CustomerBeforeMicrosoftCommonTargets属性,您可以选择指定相对于$(SolutionDir)属性的完整路径。
但是,如果您愿意将Foo与任何默认解决方案目标一起运行(即Build、Rebuild、Clean或Publish),则可以从MSBuild中的Web发布管道如何使用DeployOnBuild属性来调用解决方案中包含不支持发布的其他项目类型的Web项目上的Publish目标中获得一些灵感。

在这里可以了解有关 before.Solution1.sln.targets 文件的更多信息: http://sedodream.com/2010/10/22/MSBuildExtendingTheSolutionBuild.aspx


1
现在有一种更简单的方法可以在一个步骤中完成。使用Visual Studio 2017和MSBuild 15,遵循一种约定,其中Directory.Build.props和Directory.Build.targets被导入,就像在before.Blah.sln.targets方法中描述的那样。如果这个答案对你不起作用,请删除before.Blah.sln.targets文件,并将EmptyFoo.targets重命名为Directory.Build.targets。然后它应该像魔术般地工作。请参阅来自MSBuild团队的功能描述https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2017。 - Greg

2

您可以通过项目名称来定位它们,例如/t:project:target(可能需要引号,我记不清了)。

您可以通过设置环境变量MSBuildEmitSolution = 1来查找所有生成的目标...这会导致msbuild将生成的临时.metaproj文件保存到磁盘上,该文件中定义了所有这些目标,请打开它并查看 ;)


1
也许不是最好的答案,但是一个合理的技巧。
msbuild ProjectA.csproj
msbuild ProjectB.csproj /t:Foo

1
当使用msbuild构建解决方案时,msbuild仅向其.metaproj文件中发出有限的目标,并且据我所知 - 您无法通过构建sln文件构建自定义目标,您必须使用原始project1.csproj或自定义构建脚本。

0

仅供参考:

在使用MSBuildTask时,请使用ContinueOnError,或在使用(dotnet) msbuild时使用-p:ContinueOnError=ErrorAndContinue

这可能在有限的情况下有所帮助:例如,如果您有一组.csproj文件,并且只想将元数据附加到特定项目文件项,则可以编写类似以下内容的代码:

<Target Name="UniqueTargetName" Condition="'$(PackAsExecutable)' == 'Package' Or '$(PackAsExecutable)' == 'Publish'" Outputs="@(_Hello)">
  <ItemGroup>
    <_Hello Include="$(MSBuildProjectFullPath)" />
  </ItemGroup>
</Target>

<Target Name="BuildEachTargetFramework" DependsOnTargets="_GetTargetFrameworksOutput;AssignProjectConfiguration;_SplitProjectReferencesByFileExistence"
        Condition="$(ExecutableProjectFullPath) != ''">

  <Message Text="[$(MSBuildThisFilename)] Target BuildEachTargetFramework %(_MSBuildProjectReferenceExistent.Identity)" Importance="high" />

  <MSBuild
    Projects="%(ProjectReferenceWithConfiguration.Identity)"
    Targets="UniqueTargetName"
    ContinueOnError="true">
    <Output TaskParameter="TargetOutputs" ItemName="_Hallo2" />
  </MSBuild>

  <Message Text="[$(MSBuildThisFilename)] ########### HELLO %(_Hallo2.Identity)" Importance="high" />
  
</Target>

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