In MsBuild it is possible to create a build.proj.user
file which is parsed by the Microsoft.Common.Targets
build file.
I want to have a similar system in place where it is possible to have a .user file in the root of the folder and make msbuild pick up the config settings from this file.
Take for example these paths:
c:\working\build.proj.user
c:\working\solution1\build.proj.user
c:\working\solution1\project1\
c:\working\solution1\project2\
c:\working\solution1\project3\build.proj.user
c:\working\solution2\
c:\working\solution2\project1\
c:\working\solution2\project2\
I want to achieve that for solution1/project1 the file c:\working\solution1\build.proj.user
is read and for solution2/project1 the file c:\working\build.proj.user
The purpose is to allow integration test connectionstring properties to be customized per solution and or project.
The current solutions I see are:
I am not a fan of either solution and wonder if there isn't a more elegant way of achieving my goal (with msbuild).
This functionality exists in MSBuild 4.0: $([MSBuild]::GetDirectoryNameOfFileAbove(directory, filename)
Example: include a file named "Common.targets" in an ancestor directory
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Common.targets))\Common.targets"
Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Common.targets))' != '' " />
See this blog post for more details: MSBuild Property Functions
Add this to your project files:
<Import Project="build.proj.user" Condition="Exists('build.proj.user')"/>
<Import Project="..\build.proj.user" Condition="!Exists('build.proj.user') and Exists('..\build.proj.user')"/>
<Import Project="..\..\build.proj.user" Condition="!Exists('build.proj.user') and !Exists('..\build.proj.user') and Exists('..\..\build.proj.user')"/>
EDIT: You can also do it using MsBuild inline task. It is little bit slower, but more generic :) Inline tasks are supported from MsBuild 4.0
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="4.0">
<UsingTask TaskName="FindUserFile" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<CurrentDirName ParameterType="System.String" Required="true" />
<FileToFind ParameterType="System.String" Required="true" />
<UserFileName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
Log.LogMessage("FindUserFile parameters:");
Log.LogMessage("CurrentDirName = " + CurrentDirName);
Log.LogMessage("FileToFind = " + FileToFind);
while(CurrentDirName != Directory.GetDirectoryRoot(CurrentDirName) && !File.Exists(CurrentDirName + Path.DirectorySeparatorChar + FileToFind))
CurrentDirName = Directory.GetParent(CurrentDirName).FullName;
if(File.Exists(CurrentDirName + Path.DirectorySeparatorChar + FileToFind))
UserFileName = CurrentDirName + Path.DirectorySeparatorChar + FileToFind;
Log.LogMessage("FindUserFile output properties:");
Log.LogMessage("UserFileName = " + UserFileName);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="FindUserFileTest" >
<FindUserFile CurrentDirName="$(MSBuildThisFileDirectory)" FileToFind="build.proj.user">
<Output PropertyName="UserFileName" TaskParameter="UserFileName" />
</FindUserFile>
<Message Text="UserFileName = $(UserFileName)"/>
<Error Condition="!Exists('$(UserFileName)')" Text="File not found!"/>
</Target>
</Project>
How it works: FindUserFile is inline task written in C# language. It tries to find file specified in FileToFind parameter. Then iterate trough all parent folders and it returns the first occurrence of the FileToFind file in UserFileName output property. UserFileName output property is empty string if file was not found.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With