在MSBuild中,我可以在MetaData项目上使用String.Replace函数吗?

51

在MSBuild v4中,可以在属性上使用函数(例如string.replace),但我该如何在元数据上使用函数呢?

我想以下面的方式使用string.replace函数:

<Target Name="Build">
  <Message Text="@(Files->'%(Filename).Replace(&quot;.config&quot;,&quot;&quot;)')" />
</Target>   

不幸的是,这个输出结果并不完全符合我的预期:

log4net.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration.Replace(".config","");dataProductsGraphConfiguration.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration

有什么想法吗?


你实际上想要达到什么目的?你是希望从项目组中删除项目的扩展名吗?你可以考虑创建一个新的项目组,从原始项目组修改条目。如果需要更多控制,则可以使用转换或自定义任务来完成此操作。 - Brian Walker
你好。最终你解决了这个问题吗?怎么解决的?我也遇到了类似的问题:属性定义不起作用,因为目标有一个文件列表作为输入,而不是单个文件名。 - superjos
6个回答

79

你可以通过一些技巧来实现这个:

$([System.String]::Copy('%(Filename)').Replace('config',''))

基本上,我们调用静态方法“Copy”来创建一个新的字符串(如果您只尝试$('%(Filename)'.Replace('.config','')),某些情况下它不会喜欢这样),然后在字符串上调用replace函数。

完整文本应该是这样的:

<Target Name="Build">
        <Message Text="@(Files->'$([System.String]::Copy(&quot;%(Filename)&quot;).Replace(&quot;.config&quot;,&quot;&quot;))')" />
</Target>

编辑:看起来MSBuild 12.0破坏了上述方法。作为替代方法,我们可以向所有现有的Files项添加一个新的元数据条目。在定义元数据项时执行替换,然后就可以像访问任何其他元数据项一样访问修改后的值。

例如:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <ItemGroup>
        <Files Include="Alice.jpg"/>
        <Files Include="Bob.not-config.gif"/>
        <Files Include="Charlie.config.txt"/>
    </ItemGroup>

    <Target Name="Build">
        <ItemGroup>
            <!-- 
            Modify all existing 'Files' items so that they contain an entry where we have done our replace.
            Note: This needs to be done WITHIN the '<Target>' (it's a requirment for modifying existing items like this
            -->
            <Files>
                <FilenameWithoutConfig>$([System.String]::Copy('%(Filename)').Replace('.config', ''))</FilenameWithoutConfig>
            </Files>
        </ItemGroup>

        <Message Text="@(Files->'%(FilenameWithoutConfig)')" Importance="high" />
    </Target>
</Project>

结果:

D:\temp>"c:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe" /nologo test.xml
Build started 2015/02/11 11:19:10 AM.
Project "D:\temp\test.xml" on node 1 (default targets).
Build:
  Alice;Bob.not-config;Charlie
Done Building Project "D:\temp\test.xml" (default targets).

2
使用MSBuild 12.0(和.NET 4.0),我得到了"%(Filename);%(Filename);.."这样的结果。我的环境出了什么问题? - nilleb
@nilleb 你能把相关的代码发布到某个地方(比如pastebin.com),我会看看是否能找出问题所在。 - Grant Peters
@nilleb 看起来他们在2012年改变了评估顺序,我会很快发布一个2012年的解决方法。 - Grant Peters
5
我建议使用 $([MSBuild]::ValueOrDefault('%(MetadataName)', '').Replace('foo', 'bar')) 替代 String.Copy,以避免实际创建字符串数据的副本。 - Daniel Plaisted
3
请注意:MSBuild的GitHub存储库上有一个相关的未解决问题:元数据应支持实例方法 - 0xced
显示剩余2条评论

47

我需要做类似的事情,以下方法适用于我。

<Target Name="Build">
  <Message Text="@(Files->'%(Filename)'->Replace('.config', ''))" />
</Target>

2
这似乎是最简洁的答案。 - Thomson
3
我觉得这是最好的答案。简明扼要。 - Matt Varblow
2
这个使用了MSBuild项目函数,我认为它是最干净和最惯用的。它也不像其他替代方案那样产生字符串复制。更多信息请参见https://learn.microsoft.com/en-us/visualstudio/msbuild/item-functions。 - kzu

23

据我所知,这些函数仅适用于属性。因此,创建一个目标,通过批处理执行操作:

<Target Name="Build"                                 
      DependsOnTargets="ProcessFile" />

<Target Name="ProcessFile"
       Outputs="%(Files.Identity)">
   <PropertyGroup>
       <OriginalFileName>%(Files.Filename)</OriginalFileName>
       <ModifiedFileName>$(OriginalFileName.Replace(".config",""))</ModifiedFileName>
   </PropertyGroup>
   <Message Text="$(ModifiedFileName)" Importance="High"/>
</Target>

在你的例子中,你真的需要这种类型的任务吗?我是说存在 MSBuild常见项元数据

编辑: 我应该说明这个任务处理@(Files)中的所有项目


我不知道你可以基于另一个属性创建属性。非常非常有用! - willem
2
抱歉,Files.FileName是一个集合,但$ModifiedFileName最终只是单个值。我需要“Replace”在“Files”的每个项目上运行。 - willem
它处理@(Files)中的每个项目。 - Sergio Rykov
你的意思是在元数据中存储多个值吗?请举例说明你使用的项目。 - Sergio Rykov
这是仅适用于MSBuild 4.0的技巧吗?还是它可以在MSBuild 3.5(2.0)上工作?.Replace是感兴趣的操作命令。 - granadaCoder
在我尝试许多选项来实现替换的过程中,最终成功的是这个。谢谢! - Mike

2

我认为你不能直接在项目组和元数据中使用函数(那将很容易) 但是你可以使用批处理:

从这篇文章中获取灵感: array-iteration

我试图修剪一个项目组以发送到命令行工具(我需要删除文件名中的“.server”)

<Target Name="ProcessFile"  DependsOnTargets="FullPaths">

    <ItemGroup>
        <Environments Include="$(TemplateFolder)\$(Branch)\*.server.xml"/>
    </ItemGroup>

    <MSBuild Projects=".\Configure.msbuild" 
             Properties="CurrentXmlFile=%(Environments.Filename)"
             Targets="Configure"/>

</Target>

<Target Name="Configure" DependsOnTargets="FullPaths">
    <PropertyGroup>
        <Trimmed>$(CurrentXmlFile.Replace('.server',''))</Trimmed>
    </PropertyGroup>

    <Message Text="Trimmed: $(Trimmed)"/>

    <Exec  Command="ConfigCmd $(Trimmed)"/>
</Target>

1
对于 MSBuild 12.0,这里有一个替代方案。
<Target Name="Build">
    <Message Text="$([System.String]::Copy(&quot;%(Files.Filename)&quot;).Replace(&quot;.config&quot;,&quot;&quot;))" />
</Target>

0

遇到了同样的问题(除了MakeRelative),所以我使用另一种解决方案:使用老旧的CreateItem,它接受一个字符串并将其转换为Item :)

        <ItemGroup>
            <_ToUploadFTP Include="$(PublishDir)**\*.*"></_ToUploadFTP>
        </ItemGroup>
        <CreateItem Include="$([MSBuild]::MakeRelative('c:\$(PublishDir)','c:\%(relativedir)%(filename)%(_ToUploadFTP.extension)'))">
            <Output ItemName="_ToUploadFTPRelative" TaskParameter="Include"/>
        </CreateItem>
        <FtpUpload Username="$(UserName)" 
                   Password="$(UserPassword)" 
                   RemoteUri="$(FtpHost)"
                   LocalFiles="@(_ToUploadFTP)"
                   RemoteFiles="@(_ToUploadFTPRelative->'$(FtpSitePath)/%(relativedir)%(filename)%(extension)')"
                   UsePassive="$(FtpPassiveMode)" ></FtpUpload>

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