How can I get MSBUILD to evaluate and print the full path when given a relative path?
Asked Answered
V

5

62

How can I get MSBuild to evaluate and print in a <Message /> task an absolute path given a relative path?

Property Group

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

Task

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

Output

Copying '' to 'c:\Program Files (x86)\Program\'

Vachil answered 7/10, 2008 at 1:45 Comment(3)
I think that FullPath metadata only applies to <ItemGroup> items, not <PropertyGroup> properties.Nydia
I think you are right, anyone know a way to get from the Property to an full/absolute path?Vachil
You could "kinda" get there by using $(ProjectDir)$(Source_Dir), but you'd have superfluous '..'sNydia
A
109

In MSBuild 4.0, the easiest way is the following:

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

This method works even if the script is <Import>ed into another script; the path is relative to the file containing the above code.

(consolidated from Aaron's answer as well as the last part of Sayed's answer)


In MSBuild 3.5, you can use the ConvertToAbsolutePath task:

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

Relevant output:

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

A little long-winded if you ask me, but it works. This will be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.


In MSBuild 2.0, there is an approach which doesn't resolve "..". It does however behave just like an absolute path:

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

The $(MSBuildProjectDirectory) reserved property is always the directory of the script that contains this reference.

This will also be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.

Angling answered 9/8, 2009 at 11:22 Comment(4)
Be careful with [System.IO.Path]::GetFullPath. I ran in to an error building in Visual Studio because the current working directory in msbuild was C:\Windows\System32. GetFullPath resolves relative to the working directory rather than the project directory.Halvaard
@ChrisChilvers That's unfortunate. Last time I tested this (admittedly a long time ago), it was relative to the file containing the code.Angling
It also works for backtracing ..\..\your\path style paths. Also note that the $(MSBuildThisFileDirectory) macro already includes a trailing slash, so you must specify your\path without the leading slash i.e $(MSBuildThisFileDirectory)your\path. One case where this matters is when you use it for an OutDir for a Microsoft Unit Testing Framework Test, when you try to run the tests it will be unable to evaluate the `\\` in the concatenated path to find the built dll.Preempt
@AdamYaxley GetFullPath automatically collapses double backslashes, so it will work even if you have that extra backslash. Your issue must have been due to something else.Angling
H
35

MSBuild 4.0 added Property Functions which allow you to call into static functions in some of the .net system dlls. A really nice thing about Property Functions is that they will evaluate out side of a target.

To evaluate a full path you can use System.IO.Path.GetFullPath when defining a property like so:

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

The syntax is a little ugly but very powerful.

Hydrated answered 4/2, 2011 at 4:5 Comment(3)
It also evaluates the path relatively to (as far as I see) the project file where the property is defined, which is very nice when you want to include that property in other files. For me, it was the perfect solution.Hardnett
@JeanHominal If you use <Import>, it's still relative to the place where it's imported to, which is a shame. Still, this is the only approach that also works outside of targets, which gets it a +1 from me. See this answer for a way to work around the <Import> issue.Angling
Yes, I was mistaken when I said what I did - all relative paths are always relative to the "executing project" path (the project that is currently being executed by MSBuild); however, you can use $(MSBuildThisFileDirectory) to get a full path to the currently executing file's directory.Hardnett
H
8

Wayne is correct that well-known metadata does not apply to properties - only to items. Using properties such as "MSBuildProjectDirectory" will work, but I'm not aware of a built in way to resolve the full path.

Another option is to write a simple, custom task that will take a relative path and spit out the fully-resolved path. It would look something like this:

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

And your MSBuild lines would look something like:

<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>
Host answered 7/10, 2008 at 3:32 Comment(1)
Dude, I have learnt more about how to build an MS Task from that piece of code above, then I ever have in the MSBuild documentation. Thank you! :-)Cloison
R
5

You are trying to access an item metadata property through a property, which isn't possible. What you want to do is something like this:

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

Which will generate output as:

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

(The script was run from my Documents folder, so ..\Desktop is the correct relative path to get to my desktop.)

In your case, replace the "..\Desktop" with "......\Public\Server" in the Source_Dir item and you should be all set.

Rowenarowland answered 7/10, 2008 at 3:38 Comment(1)
+1 This works nicely and I (like others) landed here searching for a way to canonicalize an ItemGroup (pretty much assuming a batch and copy to a new ItemGroup was necessary) - the syntax you show does it without that confusion. IOW I had forgotten about the FullPath Well known metadataEntellus
L
4

If you need to convert Properties to Items you have two options. With msbuild 2, you can use the CreateItem task

  <Target Name='Build'>
    <CreateItem Include='$(Source_Dir)'>
      <Output ItemName='SRCDIR' TaskParameter='Include' />
    </CreateItem>

and with MSBuild 3.5 you can have ItemGroups inside of a Task

  <Target Name='Build'>
    <ItemGroup>
      <SRCDIR2 Include='$(Source_Dir)' />
    </ItemGroup>
    <Message Text="%(SRCDIR2.FullPath)" />
    <Message Text="%(SRCDIR.FullPath)" />
  </Target>
Lecce answered 7/10, 2008 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.