Generate targets with MSBuild then import them in

2019-09-09 05:08发布

问题:

I have an MSBuild project file that generates some of its own targets, then imports them. It works nicely, except that msbuild has to be run twice to do the build—once to generate the rules and again for them to be visible to finish the build. (The logic was ported from a Makefile that itself only needed one run.)

Is it possible to run the generating target, then Import, then run one of the generated targets?

The following listing is a trivialized version of the actual use case which exhibits the same behavior. (Beside the point, the actual case generates targets from a list of filenames of the pattern *-to-*.xsl, using the name to automatically determine the dependency and destination.)

The objective is to make msbuild /t:SecondTarget work on the first try instead of requiring a second.

<Project
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
  InitialTargets="AdditionalTargets"
  ToolsVersion="4.0">

  <!--
    A trivial target-generating task. Presume something more useful for
    the actual case.
  -->
  <UsingTask
    TaskName="GenerateAdditionalTargets"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Result ParameterType="System.String" Output="true"/>
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml"/>
      <Reference Include="System.Xml.Linq"/>
      <Using Namespace="System"/>
      <Using Namespace="System.Xml.Linq"/>
      <Code Type="Fragment" Language="cs"><![CDATA[
          var ns = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");

          var collected = new XElement(ns + "Project",
            new XAttribute("ToolsVersion", "4.0"),
            new XElement(ns + "Target",
              new XAttribute("Name", "SecondTarget"),
              new XElement(ns + "Message",
                new XAttribute("Text", "1.21GW?!")
              )
            )
          );

          Result = collected.ToString(SaveOptions.DisableFormatting);
        ]]></Code>
    </Task>
  </UsingTask>

  <PropertyGroup>
    <AdditionalTargets>additional.targets</AdditionalTargets>
  </PropertyGroup>

  <Target Name="AdditionalTargets">
    <GenerateAdditionalTargets>
      <Output TaskParameter="Result" PropertyName="Targets"/>
    </GenerateAdditionalTargets>
    <WriteLinesToFile File="$(AdditionalTargets)" Lines="$(Targets)" Overwrite="true"/>
  </Target>

  <Import Project="$(AdditionalTargets)" Condition="Exists('$(AdditionalTargets)')"/>
</Project>

回答1:

I don't think this is possible. By design when calling a task it does not have the entire context of the project. It just has what is passed in to it. It's designed this way (at least one reason) so that task invocations cannot have unforeseen consequences which are hard to identify.