Filip De Vos
Filip De Vos

Reputation: 11908

Find a file in the parent folders with msbuild

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

Upvotes: 6

Views: 5394

Answers (2)

Kevin Kibler
Kevin Kibler

Reputation: 13577

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

Upvotes: 19

Ludwo
Ludwo

Reputation: 6173

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.

Upvotes: 9

Related Questions