How can I get MSBuild to evaluate and print in a
task an absolute path given a relative path?
Property Group
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.
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>
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>
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.
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.