【问题标题】:How to re-run property evaluation in MSBuild target?如何在 MSBuild 目标中重新运行属性评估?
【发布时间】:2015-04-15 00:06:20
【问题描述】:

我有一个自定义的 MSBuild 目标,部分 sn-p 如下..

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

这是this solution 的返工尝试,因为我试图将此模板隔离到外部文件。该模板包含 MSBuild 属性引用,例如 $(ApplicationName)。当完全按照linked solution 中的描述执行此操作时,它可以正常工作,但是当将模板作为字符串加载时,在它到达文件时不会评估这些属性表达式。

<SPAN CLASS="BannerTextApplication">$(ApplicationName)</SPAN>

是否有一个 MSBuild 表达式/函数可用于在调用 Target 的上下文中重新评估字符串?

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

使用 Visual Studio 2012 和 .NET Framework 4.5。

【问题讨论】:

  • 您应该将属性/项目声明放在目标中。在msdn.microsoft.com/en-us/magazine/dd419659.aspx 上查看动态属性和项目。
  • 您正在从外部文件加载此模板。这意味着 msbuild 引擎不会在模板本身中进行任何属性扩展。因此,您的 $(PublishHtmTemplateContents) 只是从文件中读取的文本字符串。您只需将其重新分配给 $(PublishHtm) 作为文本值。引擎无法以这种方式工作。您可以尝试将模板以某种形式的目标包装在单独的文件中,然后使用 msbuild 任务构建它。
  • @SayedIbrahimHashimi,查看我的问题,我正在从目标节点中读取我的属性声明,而您的文章让我患有 TL;DR,因为 MSBuild 中有 TMI。你在说什么?你能总结一下吗?
  • @AlexeyShcherbak 我很清楚 MSBuild 并不完全以这种方式工作,这就是我发布问题的原因。 (实际上,它确实工作,除了需要重新评估的延迟加载的表达式。)您能否详细说明您的“您可以尝试..”措辞作为建议的答案?
  • 仅供参考,如果您在我的问题中点击“此解决方案”一词的超链接,您可以看到我开始的内容,而且我已经知道该方法确实有效,正如我明确表示的在我的问题文本中。我正在尝试建立一种不同的方式,一种没有在 MSBuild XML 中明确声明 HTML 标记的方式。换句话说,我对正常的约束不满意,我正在寻找一个 MSBuild API 调用或其他可以让我绕过它们而不是形式上的变通方法的东西。

标签: msbuild msbuild-task msbuild-4.0


【解决方案1】:

很抱歉有一段时间没有回答这个问题。 最初我认为要解决这个问题,我们需要以非常不寻常的方式弯曲 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>  

你也可以download these files here

【讨论】:

  • 虽然这不能直接回答问题(显然它是不可能??像我问的那样重新评估外部化的 MSBuild 属性),但您的回答确实解决了我的直接问题担心在 MSBuild 输出脚本中有无法管理的 HTML,谢谢。
  • 据我了解,在当前的 msbuild 上下文中没有直接重新评估脚本主体(包括属性)的方法。但可能值得检查 msbuild 源代码以确认或反驳 =)
  • 不幸的是,不知何故,您提出的方法并未保留其内容的细节。例如,分号在 CSS 中被删除了。我仍然希望,如果我生成一个 .targets 文件并调用 &lt;MSBuild Project="generatedfile" Targets="ProcessHtml"&gt; 或其他东西,我正在寻找的东西仍然是可行的。还没有。
  • 实际上,恢复到原始解决方案也会从样式标记中删除分号。 WriteLinesToFile 任务似乎是一件奇怪的事情。用 %3b 替换所有分号有效(是的,即使我们在 CDATA 中)。
【解决方案2】:

你可以使用 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 会进行评估!

评估任务来源:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    相关资源
    最近更新 更多