How to find the location of a .targets file?

We have a .targets file that implements a few custom build steps we need for our projects. One of those tasks is to call a command line tool to do some work with the assembly that was just compiled.

We store the .targets file and the commandline tool directly in our source control (since they are both small) so that it is very easy for developers to build. They just grab the latest source tree and they have everything they need to build.

Here is today's problem: How can our .targets file find the location of the command line tool Developers may have checked out the source code anywhere. The commandline tools is in the same directory as the .targets file, but I'm not sure how to even find the location of the .targets file from within the .targets file itself. The location of the .targets file relative to individual projects that import it varies from project to project, so I really need to find the command line tool by baseing it's location relative to the .targets file. Follow me so far Since relative paths, and reservered properties are all relative to the project doing the import, I can't use things like $(MSBuildProjectDirectory).

We do have a DLL with custom tasks in that same folder. I could write a task that exposed the location of the DLL (assuming MSBuild does not copy the DLL elsewhere prior to using it). That seems a bit hacky :)

I am a bit of an MSBuild novice, so maybe there is an easier/better way. Any other suggestions


Answer this question

How to find the location of a .targets file?

  • Aravindalochana

    Now, I wish it worked the way you decribed. Life would be much simpler if paths were always relative to the file they were in. It's not directly related to this, but we had similar problems with relative paths in project references. If a project referenced and project that referenced a project, we got errors that the most deeply nested reference could not be found be cause the relative path to the .csproj was being interpreted based on the initial project and not the middle project.

  • Thierry Tuo

    Thanks for the informative reply. It would still be nice to be able to do this in the straight ItemGroup syntax, but this is certainly better than nothing!

    Many thanks,

    Taylor


  • garry_gill

    Yes, I tried something like this. It doesn't work for me. The output is:

    Microsoft (R) Build Engine Version 2.0.50727.42
    [Microsoft .NET Framework, Version 2.0.50727.42]
    Copyright (C) Microsoft Corporation 2005. All rights reserved.

    Build started 2/10/2006 1:40:13 PM.
    __________________________________________________
    Project "C:\temp\msbuildtest\One\Two\YourProject.proj" (default targets):

    Target Print:
    Command line location: C:\temp\msbuildtest\One\Two\utils\CommandLine.txt
    Command line location: C:\temp\msbuildtest\One\Two\utils\CommandLine.txt

    Build succeeded.
    0 Warning(s)
    0 Error(s)

    Time Elapsed 00:00:00.01

    I believe this is related to this statement in the MSBuild documentation (http://msdn2.microsoft.com/en-us/library/92x05xfs.aspx):

    All relative paths in imported projects are interpreted relative to the directory of the importing project. Therefore, if a project file is imported into several project files in different locations, the relative paths in the imported project file will be interpreted differently for each importing project.

  • Robby Economides

    I was wanting to do the same thing - determine the location of the very file I am in - not the importing file. I think your solution is clever, but I keep thinking there has to be a cleaner way than this! There is no equilvalent property for THIS very file or this very directory like MSBuildProjectDirectory Is there any internal MSBuild state that is exposed in the API that I could examine to determine which file is currently running Maybe then I could write a cleaner task that would tell me which file or directory I was really in

    Many thanks,

    Taylor


  • Anu Viswan

    After looking into this a bit more, there is a much better way. You can get the location of the current project file from the task using the BuildEngine.ProjectFileOfTaskNode property. From that you can determine the true directory that you are in. Here is the new task:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    namespace CurrentDirectory
    {
    public class CurrentDir : AppDomainIsolatedTask
    {
    private ITaskItem currentDir;
    [Output]
    public ITaskItem CurrentDirectory
    {
    get
    {
    return this.currentDir;
    }
    }
    public override bool Execute()
    {
    System.IO.FileInfo projFile = new System.IO.FileInfo(base.BuildEngine.ProjectFileOfTaskNode);
    this.currentDir = new TaskItem(projFile.Directory.FullName);

    return true;
    }
    }
    }


    I've posted a blog related to this, and all the related files are available there as well if you'd like to download http://www.sedodream.com/PermaLink,guid,020fd1af-fb17-4fc9-8336-877c157eb2b4.aspx

    Sayed Ibrahim Hashimi
    www.sedodream.com

  • Chris Peeters

    Hi,
    Wow, I don't think I've screwed up like that in quite a while, sorry for that. But I do have a workaround for you. Its kind of a trick.
    First create this task:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    namespace CurrentDirectory
    {
    public class CurrentDir : Task
    {
    private ITaskItem currentDir;
    [Output]
    public ITaskItem CurrentDirectory
    {
    get
    {
    return this.currentDir;
    }
    }
    public override bool Execute()
    { System.IO.FileInfo assemblyFile = new System.IO.FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
    this.currentDir = new TaskItem( assemblyFile.Directory.FullName );
    return true;

    }
    }
    }


    Build this into an assembly, I called it CurrentDirectory.dll

    Place this task into the directory that you need to get to. In the SharedTargets.targets file use it to get to the path.

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!--
    <ItemGroup>
    <CommandLineUtil Include="utils\CommandLine.txt"/>
    </ItemGroup>
    -->

    <UsingTask AssemblyFile="CurrentDirectory.dll" TaskName="CurrentDir"/>
    <PropertyGroup>
    <CommandLineFullPath>@(CommandLineUtil->'%(FullPath)')</CommandLineFullPath>
    </PropertyGroup>


    <Target Name="SharedTarget">
    <CreateItem Include="utils\CommanLine.txt">
    <Output ItemName="CommandLineUtil" TaskParameter="Include"/>
    </CreateItem>

    <CurrentDir>
    <Output ItemName="CurrentDir" TaskParameter="CurrentDirectory" />
    </CurrentDir>


    <Message Text="Inside the shared target" Importance="high"/>
    <Message Text="Location: @(CurrentDir->'%(Fullpath)')"/>
    </Target>

    </Project>

    Here is the output:
    __________________________________________________
    C:\temp\MSBuildDirectoryExample\One\Two>msbuild YourProject.proj /t:sharedtarget

    Microsoft (R) Build Engine Version 2.0.50727.42
    [Microsoft .NET Framework, Version 2.0.50727.42]
    Copyright (C) Microsoft Corporation 2005. All rights reserved.

    Build started 2/10/2006 3:16:04 PM.
    __________________________________________________
    Project "C:\temp\MSBuildDirectoryExample\One\Two\YourProject.proj" (sharedtarget
    target(s)):

    Target SharedTarget:
    Inside the shared target
    Location: C:\temp\MSBuildDirectoryExample\Shared

    Build succeeded.
    0 Warning(s)
    0 Error(s)


    Obviously this is not the ideal resolution, and there may be better ways. Just threw this together, if this doesn't work for you let me know. I'll update that zip file later 2nite.

    Sayed Ibrahim Hashimi
    www.sedodream.com




  • Saikalyan

    This post contained incorrect information



  • Alberto Pardo

    Perhaps there will be a more intuitive method in the next release. I know that others have asked about this very same topic previously.

    Sayed Ibrahim Hashimi
    www.sedodream.com


  • Jogesh Grover

    Not sure if this is what you're trying to do, but the problem I had that was similar was Trying to build several child projects from a main but the child projects referenced dll's using relative paths, and thos paths weren't valid from my main build file.

    I had to manually edit the child project file and change the reference like so:

    <ItemGroup>
    <Reference Include="log4net">
    <Name>log4net</Name>
    <HintPath>$(MSBuildProjectDirectory)\..\..\..\SharedLibs\log4net\log4net.dll</HintPath>
    </Reference>
    <Reference Include="System">
    <Name>System</Name>
    </Reference>
    <Reference Include="System.Data">
    <Name>System.Data</Name>
    </Reference>
    <Reference Include="System.Xml">
    <Name>System.XML</Name>
    </Reference>
    <ProjectReference Include="..\Castle.Services.Logging\Castle.Services.Logging-vs2005.csproj">
    <Name>Castle.Services.Logging</Name>
    <Project>{1AFAEC30-152C-43E2-B37C-E713419D20B8}</Project>
    <Package>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</Package>
    </ProjectReference>
    </ItemGroup>

    Now when I do my build the path will resolve correctly...it would be wonderful if vs would give you some measure of control as to how it adds the reference like letting you specify variables or something similar


  • How to find the location of a .targets file?