Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • Make a custom msbuild task which will go look for this file
  • Construct a shell command to find the file.
  • Have it hard-coded look in the parent and parent of parent path

I am not a fan of either solution and wonder if there isn't a more elegant way of achieving my goal (with msbuild).

like image 362
Filip De Vos Avatar asked Nov 28 '22 09:11

Filip De Vos


2 Answers

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

like image 120
Kevin Kibler Avatar answered Dec 06 '22 14:12

Kevin Kibler


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.

like image 23
Ludwo Avatar answered Dec 06 '22 14:12

Ludwo