如何在MSBuild目标中重新运行属性评估?

6

我有一个自定义的 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属性引用,例如$(ApplicationName)。按照链接的解决方案中所描述的完全进行操作时,它可以正常工作,但是当将模板作为字符串加载时,这些属性表达式在到达文件时都没有被计算。
<SPAN CLASS="BannerTextApplication">$(ApplicationName)</SPAN>

有没有一种MSBuild表达式/函数可以在调用目标的上下文中重新评估字符串?

顺便说一句,我宁愿不使用查找/替换或正则表达式替换来解决问题,而是坚持使用MSBuild表达式引擎。

使用Visual Studio 2012和.NET Framework 4.5。


1
你应该将属性/项声明放在目标内部。请参阅 https://msdn.microsoft.com/en-us/magazine/dd419659.aspx 上的动态属性和项。 - Sayed Ibrahim Hashimi
你正在从外部文件加载此模板。这意味着msbuild引擎不会在模板本身中执行任何属性扩展。因此,你的$(PublishHtmTemplateContents)只是从文件中读取的文本字符串。然后,你只需将其重新分配给$(PublishHtm)作为文本值。引擎不会以这种方式工作。你可以尝试在单独的文件中将模板包装在某种形式的目标中,然后使用msbuild任务构建它。 - Alexey Shcherbak
@SayedIbrahimHashimi,看着我的问题,我正在从目标节点中读取我的属性声明,而你的文章让我感到TL;DR,因为在MSBuild中有太多信息了。你想表达什么?你能简要概括一下吗? - Jon Davis
请注意,如果你在我的问题中关注“此解决方案”单词的超链接,你就可以看到我最初使用的内容。我已经明确指出那种方法有效。我正在尝试建立一种不同的方式,一种不需要在 MSBuild XML 中显式声明 HTML 标记的方式。换句话说,我不满足于正常的限制,并且正在寻找一个 MSBuild API 调用或者其他可以让我规避这些限制但是不会有变通形式的东西。 - Jon Davis
如果您能添加一些我们可以使用的示例,那将会很有帮助 - 鉴于您并未直接使用“此”解决方案 - 这个问题缺少一些“实质性”的内容来尝试和错误潜在的解决方案。以下内容将有所帮助:简化当前模板HTML、所需输出的示例以及您正在使用的基本构建脚本。另外,了解您想要应用的限制也是很好的 - 不使用正则表达式是明确的,但是对于内联任务或msbuild的外部库,C#编码的限制是什么? - Alexey Shcherbak
显示剩余2条评论
2个回答

4

很抱歉没有及时回复这个问题。

最初我认为解决这个问题需要以非常不寻常的方式来弯曲MSBuild(今天的计划是编写复杂的内联任务,使用Msbuild属性作为令牌在外部文件中进行正则表达式替换)。但我认为可以更容易地使用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>  

以下是您的模板。它不是纯html,仍然是msbuild文件,但至少没有在xml中使用丑陋的编码来表示html标签。它只是CDATA块。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Transform">
    <PropertyGroup>
            <HtmlProperty><![CDATA[
                <body>
                    <div>$(MyOtherProperty)</div>
                    <div>$(Version)</div>
                    <div>$(ProjectName)</div>
                    <div>$(Author)</div>
                </body>
            ]]></HtmlProperty>
        </PropertyGroup>

    <Target Name="Transform">
        <Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
    </Target>
</Project>  

也许它并不是非常优雅(个人不太喜欢@PropsToPass部分),但这样做可以完成工作。您可以将所有内容内联到单个文件中,然后无需将属性传递给MSBuild任务。我不喜欢所提议的“此解决方案”中大量的HTML编码,而更喜欢在同一脚本中保留HTML模板,并以漂亮的HTML格式进行转换,而不需要编码。
单文件示例:
<?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">
        <PropertyGroup>
            <HtmlProperty><![CDATA[
                <body>
                    <div>$(MyOtherProperty)</div>
                    <div>$(Version)</div>
                    <div>$(ProjectName)</div>
                    <div>$(Author)</div>
                </body>
            ]]></HtmlProperty>
        </PropertyGroup>

        <Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
    </Target>
</Project>  

你可以在这里下载这些文件

虽然这并没有直接回答我的问题(显然不可能像我所问的那样重新评估外部化的MSBuild属性),但是你的回答确实解决了我在MSBuild输出脚本中存在难以管理的HTML的紧急问题,所以谢谢。 - Jon Davis
据我所知,在当前的MSBuild上下文中,没有直接重新评估脚本主体(包括属性)的方法。但是检查MSBuild源代码以确认或证明这一点可能是值得的 =) - Alexey Shcherbak
不幸的是,你提出的方法在保留细节方面存在问题。例如,在 CSS 中会丢失分号。 我仍然期望,如果我生成一个 .targets 文件并调用 <MSBuild Project="generatedfile" Targets="ProcessHtml"> 或类似的东西,我仍然可以实现我要找的东西。但现在还没有到那一步。 - Jon Davis
1
实际上,还原到原始解决方案会从样式标记中删除分号。似乎是WriteLinesToFile任务的奇怪问题。用%3b替换所有分号可以解决这个问题(是的,即使我们在CDATA中)。 - Jon Davis

1
你可以使用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>

实际上,这个任务除了返回它收到的完全相同的值之外,什么也不做。但是,当你将返回的值 %(EvalItemTemp.Identity) 传递到任何地方时,msbuild会进行评估!
Eval 任务源代码:
<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>

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;
        }
    }
}

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