79

我在 Visual Studio 中有一个包含多个项目的解决方案。我想在每次构建开始时运行一个命令 - 无论涉及哪些项目以及它们是否是最新的。

本质上,我需要类似于解决方案范围的预构建事件,但不幸的是 VS 似乎不支持这些。有谁知道实现我需要的另一种方法?

4

7 回答 7

45

不寻常的要求。但这是可以完成的。向您的解决方案添加一个新项目,使用 Visual C++ > General > Makefile Project 模板。将其 NMake > Build Command Line 设置设置为您要执行的命令。使用 Project > Project Dependencies 使所有其他项目都依赖它。

于 2010-02-19T10:31:05.237 回答
44

下面是我的变体的简短概述

只是一个说明:它是所有现有的不完整列表(另见其他答案等),我只支持我在实际状态下的原始技巧......

概括

笔记:

  • 1 - 不需要任何额外的扩展。但它可能只能通过项目级别工作,所以我们用它来模拟我们的解决方案级别......对于常见的解决方案来说很难和不方便,但是是变体的。见下文。
  • 2 - vsSolutionBuildEvent的原始引擎提供了几种统一支持VS和msbuild.exe的方式。targets mode调用仅适用于 msbuild.exe的简单方法after.<name>.sln.targets(这不需要额外的步骤,只需操作)。但只有原始引擎(inc. vsCommandEvent)可能允许额外的脚本支持,例如(7zip 存档器、没有 nuget.exe 的 nuget 包打包、远程服务器等)。但是,这对我们的问题/问题并不重要,如果您在上面看到,您可以使用任何可用的选项来支持解决方案级别+

变体 1:Microsoft.VisualStudio.Shell.Interop

此变体不适用于 VS 的简单用户。但是,它对您的完整解决方案等很有用。

您应该实施,例如:

例如:

public sealed class YourPackage: Package, IVsSolutionEvents, IVsUpdateSolutionEvents2
{
...
    public int UpdateSolution_Begin(ref int pfCancelUpdate)
    {
        //TODO:
    }
}

然后,使用“Advise”方法注册处理程序作为优先级侦听器,即对于IVsUpdateSolutionEvents2,您应该使用AdviseUpdateSolutionEvents

这很重要,因为BuildEvents(请参阅EnvDTE) - 可能无济于事,而且可能工作得太晚 -示例

AdviseUpdateSolutionEvents 示例:

// http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivssolutionbuildmanager2.aspx
private IVsSolutionBuildManager2 sbm;

// http://msdn.microsoft.com/en-us/library/bb141335.aspx
private uint _sbmCookie;
...

sbm = (IVsSolutionBuildManager2)ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager));
sbm.AdviseUpdateSolutionEvents(this, out _sbmCookie);

在哪里:

  • sbm字段应作为 GC 保护类的一部分。
  • 要获得 SVsSolutionBuildManager 服务,请使用 ServiceProvider,但可以根据需要。见msdn

现在我们可以一次处理所有项目 - 解决方案级别。

变体 2:项目的目标和地图。

好的,你喜欢这样的东西 - MSBuild: Extending the solution build,但这个变体可能适用于来自 msbuild.exe 而不是来自 VS IDE 的构建过程......

但是当构建操作开始时,VS 也会在项目文件(*.csproj、*.vcxproj、..)中使用目标(构建、重建、清理、..)。所以我们也可以试试这个,但请记住:

  • VS 也忽略了惊人的 .sln 文件。它使用 EnvDTE 等从加载环境中形成所有最终目标。
  • .sln 应仅由 msbuild.exe 处理为:自动生成 .metaproj(默认在内存中),其中包含将构建的“内容和时间”。如果存在,包括所有项目的共同目标,例如:
...
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*" Condition="'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter')" />
<Import Project="D:\tmp\p\after.name.sln.targets" Condition="exists('D:\tmp\p\after.name.sln.targets')" />
<Target Name="Build" />
<Target Name="Rebuild" />
<Target Name="Clean" />
<Target Name="Publish" />
  • 是的,VS IDE 也无法查看 .metaproj。

因此,要使用 VS IDE 中的常见目标,您只能使用有一些限制的项目文件(这意味着不修改/扩展 VS)。

因此,如果您需要通用解决方案(即您可能不了解项目等 -例如,对于某些盒式解决方案和类似的解决方案):

  • 将您的通用 .targets 文件添加到您的所有项目中(可以使用任何工具自动添加,包括NuGet 事件等),例如:<Import Project="..\<SolutionFile>.targets" />
  • 然后,您应该对以下情况使用一些限制:
    • “仅 - 在所有项目之前”
    • “只有 - 毕竟项目”

例如,是的,它可以是“项目地图”:

  • 项目地图”说明了解决方案范围的 PRE/POST“事件”,用于来自 Visual Studio IDE 的构建操作(即来自 VS IDE 的主要)
...
<Target Name="_Build" BeforeTargets="Build" DependsOnTargets="ProjectsMap">
    <CallTarget Targets="_BuildPRE" Condition="$(ScopeDetectFirst)" />
    <CallTarget Targets="_BuildPOST" Condition="$(ScopeDetectLast)" />
</Target>
<Target Name="_BuildPRE">
    <!-- ... -->
</Target>
<Target Name="_BuildPOST">
    <!-- ... -->
</Target>
...

一般来说,我们将使用项目图,现在我们知道“什么和什么时候”应该发生。对于所有或大多数情况(更改构建顺序或从解决方案中删除任何项目)都是安全的。然而!您应该<Import>在第一个 init 中管理新项目的部分。这真的很不方便,但也是变体......

变体 3:插件 vsSolutionBuildEvent

今天,它是处理大量事件的最完整解决方案,作为事件捕捉器,具有各种高级操作,用于维护您的项目和库,在运行时从 Visual Studio 和 MSBuild 工具构建流程和流程。

解决方案中所有子项目的不同操作类型同时作为解决方案事件或单独用于每个。

https://visualstudiogallery.msdn.microsoft.com/0d1dbfd7-ed8a-40af-ae39-281bfeca2334/

插件 - vsSolutionBuildEvent

它是如何在内部工作的

如果您想使用上面的Variant 1或需要了解如何使用 Shell.Interop、EnvDTE、IVsUpdateSolutionEvents2、MSBuild Engine 等,请参见此处

方案

变体 4.EnvDTE.CommandEvents

此变体也不适合 VS 的简单用户。但是,对于变体 1,它可能对您的盒子解决方案等有用。

它不一样,但是是的,EnvDTE.CommandEvents也可以像上面的Variant 1一样。

您应该已经知道(见上文)此解决方案,以便优先使用当前类型的构建操作...那么为什么不将其用作当前问题的主要解决方案呢?

_cmdEvents.BeforeExecute += (string guid, int id, object customIn, object customOut, ref bool cancelDefault) => {

    if(UnifiedTypes.Build.VSCommand.existsById(id)) {
        // ... your action
    }

};

在哪里: Description | guid | id |In |Out| --------------------------|---------------------------------------|-----|---|---| Started: Build Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 882 | | | Started: Rebuild Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 883 | | | Started: Clean Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 885 | | |

http://vsce.r-eg.net/doc/Features/Solution-wide/

此外,如果需要,您可以选择禁止此命令。在下面的变体中,您将看到这种方式的完整解决方案。

变体 5. 插件 vsCommandEvent

https://visualstudiogallery.msdn.microsoft.com/ad9f19b2-04c0-46fe-9637-9a52ce4ca661/

它还提供了大多数事件的高级处理程序,但与第一个不同的是,它专门用于 MS Visual Studio,用于处理所有命令和输出数据作为此管理器的高级工作。不仅适用于项目和解决方案,还适用于整个 Visual Studio IDE。

一般来说,它是变体 4的常见解决方案,您可以简单地覆盖上面的所有命令来解决这个问题。

对于与 vsSolutionBuildEvent 中相同的 Event-Actions 模型,它在大多数情况下都很有用。

方案

“帮我处理变体”

所有这些变体都有开放的实现。看到这里并微笑

于 2013-08-19T09:57:22.247 回答
17

您可以查看这篇文章:MSBuild:扩展解决方案构建。

似乎正是您所需要的。

于 2011-04-19T17:49:38.700 回答
9

我们通过添加一个空项目并为此项目设置构建事件来做到这一点。然后,您必须将每个项目依赖项赋予这个空项目,以确保它每次都被构建。

于 2010-02-19T10:24:35.190 回答
1

另一个旧帖子,但受到@reg 解决方案的启发,我想运行一个简单的构建计时器,该计时器将记录解决方案构建的经过时间。当 Visual Studio IDE 启动时,我使用 powershell 模块使构建事件工作,我通过包管理器控制台加载该模块。

所以创建一个powershell模块,如BuildEvents.psm1

<#
.SYNOPSIS
    Register solution build events

.DESCRIPTION
    Registers the OnBuildBegin and OnBuildDone events for the entire solution
    De-registers the events if called multiple times.

.EXAMPLE
    RegisterBuildEvents
#>
function RegisterBuildEvents{
  try {
    Unregister-Event -SourceIdentifier "OnBuildBegin" -Force
  } catch {
    #we don't care if this doesn't work
  }
  try {
    Unregister-Event -SourceIdentifier "OnBuildDone" -Force
  } catch {
    #we don't care if this doesn't work
  }
  $obj = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($dte.Application.Events.BuildEvents, [EnvDTE.BuildEventsClass])
  Register-ObjectEvent -InputObject $obj -EventName OnBuildBegin -Action {
    # do stuff here on build begin
    Write-Host "Solution build started!"
  } -SourceIdentifier "OnBuildBegin"
  Register-ObjectEvent -InputObject $obj -EventName OnBuildDone -Action {
    # do stuff here on build done
    Write-Host "Solution build done!" 
  } -SourceIdentifier "OnBuildDone"
}

# export the functions from the module
export-modulemember -function RegisterBuildEvents

包管理器主机初始化时导入模块:

  1. 在包管理器控制台中键入 $profile 以获取您的 powershell 配置文件的位置
  2. 浏览到磁盘上的该目录,如果没有文件,则使用上述命令返回的名称创建一个文件(例如 NuGet_profile.ps1
  3. 在记事本中打开文件并添加以下行

    Import-Module -Name <Path to your ps module>\BuildEvents -Force
    RegisterBuildEvents
    
于 2018-05-23T14:16:23.887 回答
0

对于使用 MSBUILD 15 构建的代码,最简单的方法是将 Directory.Build.targets 文件放在项目所在路径的根目录(不一定是解决方案文件夹)并对其进行自定义。即使您从 Visual Studio 中构建或使用命令提示符,它也会运行。

https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019

于 2020-09-05T04:40:29.227 回答
0

已经有一段时间了,.Net 基础设施中的一些东西发生了变化,提供了新的选择。现在我选择解决这个问题之王是 nuget 包。我将构建步骤放入包中,然后将其包含到每个项目中。有用的是,Visual Studio 包管理器提供了解决方案级别的包概述,因此很容易检查此规则。

于 2018-01-05T18:30:21.637 回答