当给定一个相对路径时,我该如何让MSBUILD评估并打印出完整路径?

62

我该如何让MSBuild在<Message />任务中评估并打印相对路径的绝对路径?

属性组

<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>

任务

<Message Importance="low" Text="Copying '$(Source_Dir.FullPath)' to '$(Program_Dir)'" />

输出

将 '' 复制到 'c:\Program Files (x86)\Program\'


我认为FullPath元数据仅适用于<ItemGroup>项,而不适用于<PropertyGroup>属性。 - lesscode
我认为你是正确的,有人知道如何从属性获取完整/绝对路径的方法吗? - Eric Schoonover
1
你可以使用$(ProjectDir)$(Source_Dir)“有点儿”到达那里,但是你会有多余的“..”。 - lesscode
5个回答

108

在MSBuild 4.0中,最简单的方式如下:

$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\your\path'))

即使脚本被嵌入到另一个脚本中<Import>,该方法也可以工作;路径是相对于包含上述代码的文件的。

(整合自Aaron的答案以及Sayed的答案的最后一部分)


在MSBuild 3.5中,您可以使用ConvertToAbsolutePath任务:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         DefaultTargets="Test"
         ToolsVersion="3.5">
  <PropertyGroup>
    <Source_Dir>..\..\..\Public\Server\</Source_Dir>
    <Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
  </PropertyGroup>

  <Target Name="Test">
    <ConvertToAbsolutePath Paths="$(Source_Dir)">
      <Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
    </ConvertToAbsolutePath>
    <Message Text='Copying "$(Source_Dir_Abs)" to "$(Program_Dir)".' />
  </Target>
</Project>

相关输出:

Project "P:\software\perforce1\main\XxxxxxXxxx\Xxxxx.proj" on node 0 (default targets).
  Copying "P:\software\Public\Server\" to "c:\Program Files (x86)\Program\".

如果你问我,这有点啰嗦,但它可以正常工作。这将相对于“原始”项目文件进行操作,因此,如果将其放置在<Import>ed的文件中,它不会相对于该文件。


在MSBuild 2.0中,有一种方法不使用“..”解析,但它的行为与绝对路径完全相同:

<PropertyGroup>
    <Source_Dir_Abs>$(MSBuildProjectDirectory)\$(Source_Dir)</Source_Dir_Abs>
</PropertyGroup>

$(MSBuildProjectDirectory) 是一个保留属性,它始终是包含此引用的脚本的目录。

这也将与“原始”项目文件相关,因此,如果放置在被 <Import> 导入的文件中,这将不是相对于该文件的。


小心使用[System.IO.Path]::GetFullPath。我在Visual Studio中构建时遇到错误,因为msbuild中的当前工作目录是C:\Windows\System32GetFullPath相对于工作目录而不是项目目录解析路径。 - Chris Chilvers
@ChrisChilvers 很不幸。上次我测试时(虽然是很久以前),它是相对于包含代码的文件的。 - Roman Starkov
它也适用于回溯 ..\..\your\path 样式的路径。另请注意,$(MSBuildThisFileDirectory) 宏已经包含尾随斜杠,因此您必须指定不带前导斜杠的 your\path,即 $(MSBuildThisFileDirectory)your\path。其中一个情况是当您将其用作 Microsoft Unit Testing Framework 测试的 OutDir 时,当您尝试运行测试时,它将无法评估连接路径中的 \\\ 来查找构建的 dll。 - Adam Yaxley
@AdamYaxley GetFullPath 会自动折叠双反斜杠,因此即使您有额外的反斜杠,它也可以正常工作。您的问题一定是由其他原因引起的。 - Roman Starkov

35

MSBuild 4.0新增了属性函数(Property Functions),这允许您调用一些 .net 系统 dlls 中的静态函数。属性函数的一个非常好的特点是它们可以在目标之外进行评估。

要评估完整路径,您可以在定义属性时使用System.IO.Path.GetFullPath,如下所示:

<PropertyGroup>
  <Source_Dir>$([System.IO.Path]::GetFullPath('..\..\..\Public\Server\'))</Source_Dir>
</PropertyGroup>

语法有些丑陋,但非常强大。


它还相对于(据我所见)定义属性的项目文件评估路径,这在您想要在其他文件中包含该属性时非常好。对我来说,这是完美的解决方案。 - Jean Hominal
@JeanHominal 如果您使用 <Import>,它仍然相对于导入的位置,这很遗憾。不过,这是唯一一个在目标之外也能正常工作的方法,这让我给它加上了 +1。请参见此答案以解决 <Import> 问题。 - Roman Starkov
1
是的,当我说出我的话时,我犯了错误 - 所有相对路径始终相对于“执行项目”的路径(即当前由MSBuild执行的项目); 但是,您可以使用$(MSBuildThisFileDirectory)来获取当前正在执行文件目录的完整路径。 - Jean Hominal
正是我所需要的。谢谢! - Cameron Chapman

8

Wayne说得对,众所周知的元数据不适用于属性 - 只适用于项目。使用诸如“MSBuildProjectDirectory”之类的属性将起作用,但我不知道有内置的方法来解析完整路径。

另一个选择是编写一个简单的自定义任务,它将采用相对路径并输出完全解析的路径。它看起来像这样:

public class ResolveRelativePath : Task
{
    [Required]
    public string RelativePath { get; set; }

    [Output]
    public string FullPath { get; private set; }

    public override bool Execute()
    {
        try
        {
            DirectoryInfo dirInfo = new DirectoryInfo(RelativePath);
            FullPath = dirInfo.FullName;
        }
        catch (Exception ex)
        {
            Log.LogErrorFromException(ex);
        }
        return !Log.HasLoggedErrors;
    }
}

你的 MSBuild 命令大致如下:

<PropertyGroup>
    <TaskAssembly>D:\BuildTasks\Build.Tasks.dll</TaskAssembly>
    <Source_Dir>..\..\..\Public\Server\</Source_Dir>
    <Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<UsingTask AssemblyFile="$(TaskAssembly)" TaskName="ResolveRelativePath" />

<Target Name="Default">
    <ResolveRelativePath RelativePath="$(Source_Dir)">
    <Output TaskParameter="FullPath" PropertyName="_FullPath" />
    </ResolveRelativePath>
    <Message Importance="low" Text="Copying '$(_FullPath)' to '$(Program_Dir)'" />
</Target>

6
老兄,我从上面那段代码中学到了更多关于如何构建 MS 任务的知识,比我在 MSBuild 文档中学到的还要多。谢谢! :-) - evilhomer

5
您正在尝试通过属性访问项目元数据属性,这是不可能的。您想要做的是像这样做:
<PropertyGroup>
  <Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<ItemGroup>
   <Source_Dir Include="..\Desktop"/>
</ItemGroup>     
<Target Name="BuildAll">
   <Message Text="Copying '%(Source_Dir.FullPath)' to '$(Program_Dir)'" />
</Target>

这将生成如下输出:

 Copying 'C:\Users\sdorman\Desktop' to 'c:\Program Files (x86)\Program\'

(该脚本是从我的文档文件夹运行的,因此.. \ Desktop是正确的相对路径可到达我的桌面。)

在您的情况下,请将“.. \ Desktop”替换为“...... \ Public \ Server”中的Source_Dir项目,然后您就可以开始了。


+1 这个很好用,我(和其他人一样)在搜索如何规范化 ItemGroup 时来到了这里(基本上假设需要批处理并复制到新的 ItemGroup)- 您展示的语法可以避免这种混乱。换句话说,我已经忘记了 FullPath [众所周知的元数据](http://msdn.microsoft.com/en-us/library/ms164313.aspx)。 - Ruben Bartelink

4
如果你需要将Properties转换为Items,有两个选项。使用msbuild 2,你可以使用CreateItem任务。
  <Target Name='Build'>
    <CreateItem Include='$(Source_Dir)'>
      <Output ItemName='SRCDIR' TaskParameter='Include' />
    </CreateItem>

使用MSBuild 3.5,你可以在任务内部拥有ItemGroups。

  <Target Name='Build'>
    <ItemGroup>
      <SRCDIR2 Include='$(Source_Dir)' />
    </ItemGroup>
    <Message Text="%(SRCDIR2.FullPath)" />
    <Message Text="%(SRCDIR.FullPath)" />
  </Target>

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