Search code examples
c#visual-studiomsbuildcsprojmsbuild-task

Can an MSBuild Item use a Property set by a Target?


I'm trying to achieve the following with MSBuild: my main project (MyProject.csproj) should include a couple Reference items, but the path to one of those References is the value of the SomeProperty property, which is set by a Target. Specifically, the value for SomeProperty is parsed from a file using ReadLinesFromFileTask.

Here is the high-level structure of MyProject.csproj:

<Project>
  <Target Name="CreateSomeProperty">
    <!-- Tasks that ultimately set $(SomeProperty) by parsing a value with ReadLinesFromFileTask -->
  </Target>
  <ItemGroup>
    <Reference Include="$(SomeProperty)" />
    <!-- Other Reference items -->
  </ItemGroup>
</Project>

Unfortunately, this setup is not working. I see those little yellow triangles under the Dependencies node of MyProject in the VS Solution Explorer, since the project is looking for a DLL at a path with missing characters. Similarly, when I build the project, I get a bunch of The type or namespace name could not be found errors, even though I still see the output from a Message Task inside my Target. Presumably, the CreatePathProperty Target is running during the execution phase, after the Reference items have already failed to load during the evaluation phase.

Is there a way to make a setup like this work? I've tried setting BeforeTargets="Build" in the Target element, and setting InitialTargets="CreateSomeProperty" in the Project element, but nothing seems to work. Any help would be much appreciated!


Solution

  • Can an MSBuild Item use a Property set by a Target?

    Yes, I'm sure it's possible if you're in .net framework project with old csproj format and what you want is a supported scenario in VS2017(Only did the test in VS2017).

    Tips:

    Normally msbuild reads the Properties and Items before it executes your custom target. So we should use something like BeforeTargets="BeforeResolveReferences" to make sure the correct order in this scenario is custom target runs=>create the property=>msbuild reads the info about references and the property.

    Otherwise the order(wrong order when BeforeTargets="Build" or what) should be: Msbuild reads the info about references(now the property is not defined yet)=>the target runs and creates the property.

    Solution: Add this script to the bottom of your xx.csproj.

      <!-- Make sure it executes before msbuild reads the ItemGroup above and loads the references -->
      <Target Name="MyTest" BeforeTargets="BeforeResolveReferences">
        <ItemGroup>
          <!-- Define a TestFile to represent the file I read -->
          <TestFile Include="D:\test.txt" />
        </ItemGroup>
        <!-- Pass the file to read to the ReadLinesFromFile task -->
        <ReadLinesFromFile File="@(TestFile)">
          <!--Create the Property that represents the normal hintpath(SomePath\DLLName.dll)-->
          <Output TaskParameter="Lines" PropertyName="HintPathFromFile" />
        </ReadLinesFromFile>
        <!-- Output the HintPath in output log to check if the path is right -->
        <Message Text="$(HintPathFromFile)" Importance="high" />
        <ItemGroup>
          <Reference Include="TestReference">
            <!--It actually equals:<HintPath>D:\AssemblyFolder\TestReference.dll</HintPath>-->
            <HintPath>$(HintPathFromFile)</HintPath>
          </Reference>
        </ItemGroup>
      </Target>
    

    In addition:

    I did the test with test.txt file whose content is:

    enter image description here

    I'm not sure about the actual content(and format) of your file, but if you only have path like D:\AssemblyFolder\ in that file, you should pass the D:\AssemblyFolder\+YourAssemblyName.dll to <HintPath> metadata. Cause the default reference format with hintpath looks like this:

      <Reference Include="ClassLibrary1">
          <HintPath>path\ClassLibrary1.dll</HintPath>
      </Reference>
    

    Pay attention to the path format! Hope my answer helps :)