如何在MSBuild目标中重新运行属性求值?

如何在MSBuild目标中重新运行属性求值?,msbuild,msbuild-task,msbuild-4.0,Msbuild,Msbuild Task,Msbuild 4.0,我有一个自定义MSBuild目标,部分代码段如下所示 <Target Name="PublishHtm"> <PropertyGroup> <PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents> <PublishHtm>$(PublishHt

我有一个自定义MSBuild目标,部分代码段如下所示

<Target Name="PublishHtm">
  <PropertyGroup>
    <PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
    <PublishHtm>$(PublishHtmTemplateContents)</PublishHtm>
  </PropertyGroup>
  <WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
在调用目标的上下文中,是否有MSBuild表达式/函数可用于获取要重新计算的字符串

顺便说一句,我不希望使用find/replace或regexreplace来解决这个问题,而是坚持使用MSBuild表达式引擎


使用Visual Studio 2012和.NET Framework 4.5。

很抱歉暂时没有回到这个问题。 起初,我认为要解决这个问题,我们需要以一种非常不寻常的方式弯曲MSBuild(今天的计划是编写复杂的内联任务,使用MSBuild属性作为标记在外部文件中进行regex替换)。但我认为使用CDATA节可以更容易地解决这个问题,CDATA节在属性定义中是有效的:

以下是主要脚本:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
    <PropertyGroup>
        <MyOtherProperty>$([System.DateTime]::Now)</MyOtherProperty>
        <Version>1.0.1b</Version>
        <ProjectName>MSBuild Rox</ProjectName>
        <Author>Alexey Shcherbak</Author>
    </PropertyGroup>

    <Target Name="Build">
        <ItemGroup>
            <PropsToPass Include="MyOtherProperty=$(MyOtherProperty)" />
            <PropsToPass Include="Version=$(Version)" />
            <PropsToPass Include="ProjectName=$(ProjectName)" />
            <PropsToPass Include="Author=$(Author)" />
        </ItemGroup>

        <MSBuild Projects="TransformHTML.Template.proj" Properties="@(PropsToPass)" />
    </Target>
</Project>  

$([System.DateTime]::现在)
1.0.1b
msbuildrox

您可以使用Eval任务来完成

<Target Name="PublishHtm">
  <PropertyGroup>
    <PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
    <Eval Values="$(PublishHtmTemplateContents)">
      <Output TaskParameter="Result" ItemName="EvalItemTemp"/>
    </Eval>
    <PublishHtm>%(EvalItemTemp.Identity)</PublishHtm>
  </PropertyGroup>
  <WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>

$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))
%(EvalItemTemp.Identity)
实际上,该任务除了返回与它接收到的值完全相同的值外,什么也不做,但是当您将返回值%(EvalItemTemp.Identity)传递到任何位置时,msbuild都会进行求值

评估任务来源:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Choose>
    <When Condition="'$(MSBuildToolsVersion)' == 'Current' OR $(MSBuildToolsVersion.Split('.')[0]) &gt;= 14">
      <PropertyGroup>
        <TasksAssemblyName>Microsoft.Build.Tasks.Core.dll</TasksAssemblyName>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <TasksAssemblyName>Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll</TasksAssemblyName>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <UsingTask TaskName="Eval" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\$(TasksAssemblyName)">
    <ParameterGroup>
      <Values ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="True" Output="False" />
      <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="False" Output="True" />
    </ParameterGroup>
    <Task>
      <Code Type="Class" Language="cs" Source="$(MSBuildThisFileDirectory)TaskSource\EvalTask.cs"/>
    </Task>
  </UsingTask>
</Project>

Microsoft.Build.Tasks.Core.dll
Microsoft.Build.Tasks.v$(MSBuildToolsVersion.dll)
其中TaskSource\EvalTask.cs是

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.Threading;

namespace Varonis.MSBuild.Tasks
{
    public class Eval : Task
    {

        [Required]
        public ITaskItem[] Values { get; set; }
        [Output]
        public ITaskItem[] Result { get; set; }

        public override bool Execute()
        {
            Result = new TaskItem[Values.Length];
            for (int i = 0; i < Values.Length; i++)
            {
                Result[i] = new TaskItem(Values[i].ItemSpec);
            }
            return true;
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用Microsoft.Build.Framework;
使用Microsoft.Build.Utilities;
使用系统诊断;
使用系统线程;
命名空间Varonis.MSBuild.Tasks
{
公共类评估:任务
{
[必需]
公共ITaskItem[]值{get;set;}
[输出]
公共ITaskItem[]结果{get;set;}
公共重写bool Execute()
{
结果=新任务项[Values.Length];
for(int i=0;i
您应该将属性/项声明放在目标中。请参阅上的动态属性和项目。您正在从外部文件加载此模板。这意味着msbuild引擎不会在模板自身中进行任何属性扩展。所以您的$(PublishHtmTemplateContents)只是从文件中读取的文本字符串。您只需将其作为文本值重新分配给$(PublishHtm)。引擎不能以这种方式工作。您可以尝试将模板以某种形式的目标包装到单独的文件中,然后使用msbuild任务构建它。@SayedIbrahimHashimi,查看我的问题我正在从目标节点中读取属性声明,而您的文章使我遭受TL;博士,因为MSBuild中有TMI。你在说什么?你能总结一个细节吗?@AlexeyShcherbak我很清楚MSBuild并不是这样工作的,这就是我发布这个问题的原因。(事实上,除了需要重新评估的延迟加载的表达式外,它确实有效。)你能详细说明你的“你可以尝试…”作为建议答案吗?仅供参考,伙计们,如果你按照我问题中“此解决方案”一词的超链接,你可以看到我开始的内容,我已经知道该方法确实有效,正如我在问题文本中明确指出的那样。我试图建立一种不同的方法,即不在MSBuild XML中显式声明HTML标记。换句话说,我对常规约束不满意,我正在寻找一个MSBuild API调用或一些可以让我绕过它们而不需要在形式上进行变通的东西。虽然这并不能直接回答这个问题(显然不可能像我问的那样重新评估外部化的MSBuild属性),您的回答确实解决了我最关心的问题,即MSBuild输出脚本中存在不可管理的HTML,因此,谢谢。据我所知,没有直接的方法在当前MSBuild上下文中重新计算脚本体(包括属性)。但可能值得检查msbuild源代码以确认或反驳=)不幸的是,您提出的方法无法保留其详细内容。例如,分号在CSS中被删除。我仍然希望,如果我生成一个.targets文件并调用
或其他什么,我所寻找的仍然是可行的。还没有。实际上,恢复到原始解决方案也会从样式标记中删除分号。从WriteListFile任务来看,这似乎是一件奇怪的事情。用%3b替换所有分号有效(是的,即使我们在CDATA中)。
<Target Name="PublishHtm">
  <PropertyGroup>
    <PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
    <Eval Values="$(PublishHtmTemplateContents)">
      <Output TaskParameter="Result" ItemName="EvalItemTemp"/>
    </Eval>
    <PublishHtm>%(EvalItemTemp.Identity)</PublishHtm>
  </PropertyGroup>
  <WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Choose>
    <When Condition="'$(MSBuildToolsVersion)' == 'Current' OR $(MSBuildToolsVersion.Split('.')[0]) &gt;= 14">
      <PropertyGroup>
        <TasksAssemblyName>Microsoft.Build.Tasks.Core.dll</TasksAssemblyName>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <TasksAssemblyName>Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll</TasksAssemblyName>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <UsingTask TaskName="Eval" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\$(TasksAssemblyName)">
    <ParameterGroup>
      <Values ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="True" Output="False" />
      <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="False" Output="True" />
    </ParameterGroup>
    <Task>
      <Code Type="Class" Language="cs" Source="$(MSBuildThisFileDirectory)TaskSource\EvalTask.cs"/>
    </Task>
  </UsingTask>
</Project>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.Threading;

namespace Varonis.MSBuild.Tasks
{
    public class Eval : Task
    {

        [Required]
        public ITaskItem[] Values { get; set; }
        [Output]
        public ITaskItem[] Result { get; set; }

        public override bool Execute()
        {
            Result = new TaskItem[Values.Length];
            for (int i = 0; i < Values.Length; i++)
            {
                Result[i] = new TaskItem(Values[i].ItemSpec);
            }
            return true;
        }
    }
}