只为没有特定包引用的项目运行一个MSBuild目标。

huangapple go评论58阅读模式
英文:

Run an MSBuild target only for projects which do not have a specific package reference

问题

I have a Directory.build.targets file which contains a <Target>...</Target> that runs before building the projects in the directory. This target checks a couple conditions (one, that a specific project property is set correctly, and the other, that a specific project reference has a metadata item set correctly). This works just fine.

However, I would like the target to skip certain projects in the directory. Specifically, I would like to skip "unit test projects", where "unit test projects" is (I guess, nowadays) defined as "has a <PackageReference> to "Microsoft.NET.Test.Sdk". That's a good enough proxy for me.

If I add a message like this:

<Message
    Text="this is the message, for $(MSBuildProjectName)"
    Importance="high"
    Condition="'%(PackageReference.FileName)' != 'Microsoft

<details>
<summary>英文:</summary>

I have a `Directory.build.targets` file which contains a `&lt;Target&gt;...&lt;/Target&gt;` that runs before building the projects in the directory. This target checks a couple conditions (one, that a specific project property is set correctly, and the other, that a specific project reference has a metadata item set correctly). This works just fine.

However, I would like the target to skip certain projects in the directory. Specifically, I would like to skip &quot;unit test projects&quot;, where &quot;unit test projects&quot; is (I guess, nowadays) defined as &quot;has a `&lt;PackageReference&gt;` to &quot;Microsoft.NET.Test.Sdk&quot;. That&#39;s a good enough proxy for me.

If I add a message like this:

    &lt;Message
        Text=&quot;this is the message, for $(MSBuildProjectName)&quot;
        Importance=&quot;high&quot;
        Condition=&quot;&#39;%(PackageReference.FileName)&#39; != &#39;Microsoft.NET.Test.Sdk&#39;&quot; /&gt;

The message is written out for each of the projects that I DO want the target to apply to. However, this condition can&#39;t be applied to the target itself; this results in the following error:

     error MSB4116: The condition &quot;&#39;%(PackageReference.FileName)&#39; != &#39;Microsoft.NET.Test.Sdk&#39;&quot; on the &quot;ValidateModuleProjectProperties&quot; target has a reference to item metadata. References to item metadata are not allowed in target conditions unless they are part of an item transform.

I tried the related (allowed) form of the condition, `@(PackageReference-&gt;&#39;$(FileName)&#39;) != &#39;Microsoft.NET.Test.Sdk&#39;`, but that doesn&#39;t work (which is probably obvious to someone who understands the difference, but I do not...).

I tried adding the condition using an `And` operator to the `&lt;Error .../&gt;` item, but that didn&#39;t seem to have any effect:

    &lt;Error
        Condition=&quot;$(EnableDynamicLoading) != true And &#39;%(PackageReference.FileName)&#39; != &#39;Microsoft.NET.Test.Sdk&#39;&quot;
        Text=&quot;The EnableDynamicLoading property must be set to true for non-test projects.&quot;
        ContinueOnError=&quot;false&quot; /&gt;

I tried putting a `&lt;Choose&gt;&lt;When Condition=&quot;&quot;&gt;...&lt;/When&gt;&lt;/Choose&gt;` inside my target, but it appears that that construct is not allowed.

How can I cause this target to only apply to non-&quot;test&quot; projects?

</details>


# 答案1
**得分**: 1

I created a new test project from the 'MSTest Test Project' template and the new project has the following:

```xml
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
    <PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
    <PackageReference Include="coverlet.collector" Version="3.1.2" />
  </ItemGroup>

The following expression is incorrect:

@(PackageReference->'$(FileName)')
  • Use Identity, not Filename. Filename excludes what appears to be a file extension. For 'Microsoft.NET.Test.Sdk', Filename is 'Microsoft.NET.Test'. Identity is the value from the Include.

  • In a transform, a metadata value starts with '%', not '$'.

e.g. with fixes applied

@(PackageReference->'%(Identity)')

But that still won't work because that evaluates to a string that is a list of all the names.

e.g. given the above PackageReferences

"'@(PackageReference->'%(Identity)')' != 'Microsoft.NET.Test.Sdk'"

evaluates to

"'Microsoft.NET.Test.Sdk;MSTest.TestAdapter;MSTest.TestFramework;coverlet.collector' != 'Microsoft.NET.Test.Sdk'"

which is always true.

A property can be set based on if the PackageReference collection has a certain item. Batching will help to do that but a Target is needed for batching.

  <Target Name="DefineIsTestProject">
    <PropertyGroup>
      <IsTestProject>false</IsTestProject>
      <IsTestProject Condition="'%(PackageReference.Identity)' == 'Microsoft.NET.Test.Sdk'">true</IsTestProject>
    </PropertyGroup>
  </Target>

The DefineIsTestProject target sets the IsTestProject property to true for test projects and false otherwise.

The %(PackageReference.Identity) is splitting the @(PackageReference) collection into batches per unique values of Identity. The property definition is run for each batch. Batching is not iteration but Identity is unique per item in the collection so effectively this is visiting and testing each item in the collection.

The following doesn't work.

  <!-- Broken - Don't use this -->
  <Target Name="ValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject">
    ...

The Condition is tested before running the Targets in the DependsOnTargets. This may seem like a 'quirk' but is actually very useful behavior.

There are two ways around this.

  1. Move the Condition to an element within the Target.
  <Target Name="ValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject">
    <Error Condition="!$(IsTestProject) and '$(EnableDynamicLoading)' != 'true'" .../>
    ...
  1. For more complex situations, create an 'orchestrator' Target that arranges the order of other Targets.
  <Target Name="RunValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject;ValidateModuleProjectProperties"/>

  <Target Name="ValidateModuleProjectProperties" Condition="!$(IsTestProject)">
    ...
英文:

I created a new test project from the 'MSTest Test Project' template and the new project has the following:

  &lt;ItemGroup&gt;
    &lt;PackageReference Include=&quot;Microsoft.NET.Test.Sdk&quot; Version=&quot;17.1.0&quot; /&gt;
    &lt;PackageReference Include=&quot;MSTest.TestAdapter&quot; Version=&quot;2.2.8&quot; /&gt;
    &lt;PackageReference Include=&quot;MSTest.TestFramework&quot; Version=&quot;2.2.8&quot; /&gt;
    &lt;PackageReference Include=&quot;coverlet.collector&quot; Version=&quot;3.1.2&quot; /&gt;
  &lt;/ItemGroup&gt;

Your project may be different. That's okay. I'm using the above as an example.

The following expression is incorrect

@(PackageReference-&gt;&#39;$(FileName)&#39;)
  • Use Identity, not Filename. Filename excludes what appears to be a file extension. For 'Microsoft.NET.Test.Sdk', Filename is 'Microsoft.NET.Test'. Identity is the value from the Include.

  • In a transform a metadata value starts with %, not $.

e.g. with fixes applied

@(PackageReference-&gt;&#39;%(Identity)&#39;)

But that still won't work because that evaluates to a string that is a list of all the names.

e.g. given the above PackageReferences

&quot;&#39;@(PackageReference-&gt;&#39;%(Identity)&#39;)&#39; != &#39;Microsoft.NET.Test.Sdk&#39;&quot;

evaluates to

&quot;&#39;Microsoft.NET.Test.Sdk;MSTest.TestAdapter;MSTest.TestFramework;coverlet.collector&#39; != &#39;Microsoft.NET.Test.Sdk&#39;&quot;

which is always true.

A property can be set based on if the PackageReference collection has a certain item. Batching will help to do that but a Target is needed for batching.

  &lt;Target Name=&quot;DefineIsTestProject&quot;&gt;
    &lt;PropertyGroup&gt;
      &lt;IsTestProject&gt;false&lt;/IsTestProject&gt;
      &lt;IsTestProject Condition=&quot;&#39;%(PackageReference.Identity)&#39; == &#39;Microsoft.NET.Test.Sdk&#39;&quot;&gt;true&lt;/IsTestProject&gt;
    &lt;/PropertyGroup&gt;
  &lt;/Target&gt;

The DefineIsTestProject target sets the IsTestProject property to true for test projects and false otherwise.

The %(PackageReference.Identity) is splitting the @(PackageReference) collection into batches per unique values of Identity. The property definition is run for each batch. Batching is not iteration but Identity is unique per item in the collection so effectively this is visiting and testing each item in the collection.

The following doesn't work.

  &lt;!-- Broken - Don&#39;t use this --&gt;
  &lt;Target Name=&quot;ValidateModuleProjectProperties&quot; BeforeTargets=&quot;BeforeBuild&quot; DependsOnTargets=&quot;DefineIsTestProject&quot; Condition=&quot;!$(IsTestProject)&quot;&gt;
    ...

The Condition is tested before running the Targets in the DependsOnTargets. This may seem like a 'quirk' but is actually very useful behavior.

There are two ways around this.

  1. Move the Condition to an element within the Target.
  &lt;Target Name=&quot;ValidateModuleProjectProperties&quot; BeforeTargets=&quot;BeforeBuild&quot; DependsOnTargets=&quot;DefineIsTestProject&quot;&gt;
    &lt;Error Condition=&quot;!$(IsTestProject) and &#39;$(EnableDynamicLoading)&#39; != &#39;true&#39;&quot; .../&gt;
    ...
  1. For more complex situations, create an 'orchestrator' Target that arranges the order of other Targets.
  &lt;Target Name=&quot;RunValidateModuleProjectProperties&quot; BeforeTargets=&quot;BeforeBuild&quot; DependsOnTargets=&quot;DefineIsTestProject;ValidateModuleProjectProperties&quot;/&gt;

  &lt;Target Name=&quot;ValidateModuleProjectProperties&quot; Condition=&quot;!$(IsTestProject)&quot;&gt;
    ...

huangapple
  • 本文由 发表于 2023年5月10日 22:43:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76219786.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定