NuGet 包:运行 安装后的 MSBUILD
NuGet Package: Run MSBUILD upon Installation
我正在创建一个 NuGet 程序包,并且有一个 .targets 文件在安装程序包时安装到目标 .csproj 文件中。这完全符合预期,.targets 文件有一个 Target 元素,该元素在 Build 和 TransformOnBuild 目标之前执行。
不过,我现在要做的是在安装包后执行这个Target。也就是说,我不希望用户在安装我的包后必须手动构建他们的 .csproj。我希望在将包安装到目标项目后将包安装到 运行 这个定义的 Target。
原因是 Target 在目标项目中自动生成了一些文件(并且基于在目标项目中找到的属性),我希望这些文件在安装时使用初始数据生成,而不是等到用户构建该项目来创建数据。
FWIW,我发现最接近这个问题的是这个问题:Configure NuGet package to add a build event when installed,但这是针对构建管道的。我想在安装 NuGet 项目后启动构建(我的 Target 在 .targets 文件中)。
编辑:
多亏了@matt-ward,我才能够得出这个最终答案。我在这里分享完整的答案。它递归地遍历项目以查找任何 .tt 文件,并将其自定义工具设置为空白,然后编译项目以生成所需的工件。这是完整的安装。ps1:
# Runs every time a package is installed in a project
param($installPath, $toolsPath, $package, $project)
# $installPath is the path to the folder where the package is installed.
# $toolsPath is the path to the tools directory in the folder where the package is installed.
# $package is a reference to the package object.
# $project is a reference to the project the package was installed to.
function AdjustCustomTool( $folder )
{
foreach ( $i in $folder.ProjectItems )
{
switch ( $i.Kind )
{
"{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}" # Folder
{
AdjustCustomTool( $i );
break;
}
"{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}" # File
{
switch ( [System.IO.Path]::GetExtension( $i.Name ) )
{
".tt"
{
$i.Properties.Item("CustomTool").Value = "";
break;
}
}
}
}
}
}
$templates = $project.ProjectItems.Item( "DragonSpark.Templates" );
AdjustCustomTool( $templates );
# Need to load MSBuild assembly if it's not loaded yet.
Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
# Grab the loaded MSBuild project for the project
$project = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$project.Build( "GenerateTemplateReferences" );
FWIW,这是我的 .targets 文件:
<Error Condition="'@(TargetReferenceFile -> Count())' == 0" Text="No DragonSpark.Templates\Generated\References.tt file found" />
<Error Condition="'@(IncludesFile -> Count())' == 0" Text="No DragonSpark.Templates\Generated\Includes.tt file found" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<# /* AUTOGENERATED FILE */ #>" Overwrite="true" Encoding="Unicode" />
<!-- T4Toolbox -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ include file="T4Toolbox.tt" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="" completion="T4Toolbox.dll" #>" Overwrite="false" Encoding="Unicode" />
<!-- Visual Studio IDE -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="VSLangProj, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.TextTemplating.Interfaces.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.TextTemplating.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" #>" Overwrite="false" Encoding="Unicode" />
<!-- With HintPath -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%24%28ProjectDir%29%(Reference.HintPath)" #>" Overwrite="false" Encoding="Unicode" Condition="'%(Reference.HintPath)' != '' And $([System.String]::new('%(Reference.HintPath)').Contains('T4Toolbox.dll')) == 'false'" />
<!-- Without HintPath -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%(Reference.Identity)" #>" Overwrite="false" Encoding="Unicode" Condition="'%(Reference.HintPath)' == ''" />
<!-- ProjectReference -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%24%28ProjectDir%29%(ProjectReference.RelativeDir)bin$(Configuration)\%(ProjectReference.Name).dll" #>" Overwrite="false" Encoding="Unicode" Condition="'%(ProjectReference.Name)' != ''" />
<!-- Includes -->
<WriteLinesToFile File="@(IncludesFile)" Lines="<# /* AUTOGENERATED FILE */ #>" Overwrite="true" Encoding="Unicode" />
<WriteLinesToFile File="@(IncludesFile)" Lines="<#@ include file="..\Includes\%(Includes.Filename)%(Includes.Extension)" #>" Overwrite="false" Encoding="Unicode" />
<Message Text="Generating %(TargetReferenceFile.FullPath) &%(IncludesFile.FullPath)" Importance="high" />
</Target>
</Project>
安装 NuGet 包后,您可以使用 PowerShell 脚本(安装。ps1)运行 构建。
PowerShell 脚本可以使用 Visual Studio API 进行构建。类似于以下内容将构建项目:
param($installPath, $toolsPath, $package, $project)
$config = $dte.Solution.SolutionBuild.ActiveConfiguration.Name
$dte.Solution.SolutionBuild.BuildProject($config, $project.UniqueName, $false)
构建特定的 MSBuild 目标更加棘手。您可以直接调用 MSBuild.exe。这是 PostSharp NuGet package 所做的事情。或者您可以使用 MSBuild API 来构建特定目标。类似于:
Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
# Grab the loaded MSBuild project for the project
$buildProject = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$buildProject.Build("YourTargetName")
请注意,我尚未测试这些 PowerShell 脚本。它们旨在为您提供指向正确实现的指针。
我正在创建一个 NuGet 程序包,并且有一个 .targets 文件在安装程序包时安装到目标 .csproj 文件中。这完全符合预期,.targets 文件有一个 Target 元素,该元素在 Build 和 TransformOnBuild 目标之前执行。
不过,我现在要做的是在安装包后执行这个Target。也就是说,我不希望用户在安装我的包后必须手动构建他们的 .csproj。我希望在将包安装到目标项目后将包安装到 运行 这个定义的 Target。
原因是 Target 在目标项目中自动生成了一些文件(并且基于在目标项目中找到的属性),我希望这些文件在安装时使用初始数据生成,而不是等到用户构建该项目来创建数据。
FWIW,我发现最接近这个问题的是这个问题:Configure NuGet package to add a build event when installed,但这是针对构建管道的。我想在安装 NuGet 项目后启动构建(我的 Target 在 .targets 文件中)。
编辑:
多亏了@matt-ward,我才能够得出这个最终答案。我在这里分享完整的答案。它递归地遍历项目以查找任何 .tt 文件,并将其自定义工具设置为空白,然后编译项目以生成所需的工件。这是完整的安装。ps1:
# Runs every time a package is installed in a project
param($installPath, $toolsPath, $package, $project)
# $installPath is the path to the folder where the package is installed.
# $toolsPath is the path to the tools directory in the folder where the package is installed.
# $package is a reference to the package object.
# $project is a reference to the project the package was installed to.
function AdjustCustomTool( $folder )
{
foreach ( $i in $folder.ProjectItems )
{
switch ( $i.Kind )
{
"{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}" # Folder
{
AdjustCustomTool( $i );
break;
}
"{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}" # File
{
switch ( [System.IO.Path]::GetExtension( $i.Name ) )
{
".tt"
{
$i.Properties.Item("CustomTool").Value = "";
break;
}
}
}
}
}
}
$templates = $project.ProjectItems.Item( "DragonSpark.Templates" );
AdjustCustomTool( $templates );
# Need to load MSBuild assembly if it's not loaded yet.
Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
# Grab the loaded MSBuild project for the project
$project = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$project.Build( "GenerateTemplateReferences" );
FWIW,这是我的 .targets 文件:
<Error Condition="'@(TargetReferenceFile -> Count())' == 0" Text="No DragonSpark.Templates\Generated\References.tt file found" />
<Error Condition="'@(IncludesFile -> Count())' == 0" Text="No DragonSpark.Templates\Generated\Includes.tt file found" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<# /* AUTOGENERATED FILE */ #>" Overwrite="true" Encoding="Unicode" />
<!-- T4Toolbox -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ include file="T4Toolbox.tt" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="" completion="T4Toolbox.dll" #>" Overwrite="false" Encoding="Unicode" />
<!-- Visual Studio IDE -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="VSLangProj, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.TextTemplating.Interfaces.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" #>" Overwrite="false" Encoding="Unicode" />
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="Microsoft.VisualStudio.TextTemplating.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" #>" Overwrite="false" Encoding="Unicode" />
<!-- With HintPath -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%24%28ProjectDir%29%(Reference.HintPath)" #>" Overwrite="false" Encoding="Unicode" Condition="'%(Reference.HintPath)' != '' And $([System.String]::new('%(Reference.HintPath)').Contains('T4Toolbox.dll')) == 'false'" />
<!-- Without HintPath -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%(Reference.Identity)" #>" Overwrite="false" Encoding="Unicode" Condition="'%(Reference.HintPath)' == ''" />
<!-- ProjectReference -->
<WriteLinesToFile File="@(TargetReferenceFile)" Lines="<#@ assembly name="%24%28ProjectDir%29%(ProjectReference.RelativeDir)bin$(Configuration)\%(ProjectReference.Name).dll" #>" Overwrite="false" Encoding="Unicode" Condition="'%(ProjectReference.Name)' != ''" />
<!-- Includes -->
<WriteLinesToFile File="@(IncludesFile)" Lines="<# /* AUTOGENERATED FILE */ #>" Overwrite="true" Encoding="Unicode" />
<WriteLinesToFile File="@(IncludesFile)" Lines="<#@ include file="..\Includes\%(Includes.Filename)%(Includes.Extension)" #>" Overwrite="false" Encoding="Unicode" />
<Message Text="Generating %(TargetReferenceFile.FullPath) &%(IncludesFile.FullPath)" Importance="high" />
</Target>
</Project>
安装 NuGet 包后,您可以使用 PowerShell 脚本(安装。ps1)运行 构建。
PowerShell 脚本可以使用 Visual Studio API 进行构建。类似于以下内容将构建项目:
param($installPath, $toolsPath, $package, $project)
$config = $dte.Solution.SolutionBuild.ActiveConfiguration.Name
$dte.Solution.SolutionBuild.BuildProject($config, $project.UniqueName, $false)
构建特定的 MSBuild 目标更加棘手。您可以直接调用 MSBuild.exe。这是 PostSharp NuGet package 所做的事情。或者您可以使用 MSBuild API 来构建特定目标。类似于:
Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
# Grab the loaded MSBuild project for the project
$buildProject = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$buildProject.Build("YourTargetName")
请注意,我尚未测试这些 PowerShell 脚本。它们旨在为您提供指向正确实现的指针。