英文:
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 `<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.NET.Test.Sdk'" />
The message is written out for each of the projects that I DO want the target to apply to. However, this condition can't be applied to the target itself; this results in the following error:
error MSB4116: The condition "'%(PackageReference.FileName)' != 'Microsoft.NET.Test.Sdk'" on the "ValidateModuleProjectProperties" 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->'$(FileName)') != 'Microsoft.NET.Test.Sdk'`, but that doesn'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 `<Error .../>` item, but that didn't seem to have any effect:
<Error
Condition="$(EnableDynamicLoading) != true And '%(PackageReference.FileName)' != 'Microsoft.NET.Test.Sdk'"
Text="The EnableDynamicLoading property must be set to true for non-test projects."
ContinueOnError="false" />
I tried putting a `<Choose><When Condition="">...</When></Choose>` inside my target, but it appears that that construct is not allowed.
How can I cause this target to only apply to non-"test" 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
, notFilename
.Filename
excludes what appears to be a file extension. For 'Microsoft.NET.Test.Sdk',Filename
is 'Microsoft.NET.Test'.Identity
is the value from theInclude
. -
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 PackageReference
s
"'@(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 Target
s in the DependsOnTargets
. This may seem like a 'quirk' but is actually very useful behavior.
There are two ways around this.
- Move the
Condition
to an element within theTarget
.
<Target Name="ValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject">
<Error Condition="!$(IsTestProject) and '$(EnableDynamicLoading)' != 'true'" .../>
...
- For more complex situations, create an 'orchestrator'
Target
that arranges the order of otherTarget
s.
<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:
<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>
Your project may be different. That's okay. I'm using the above as an example.
The following expression is incorrect
@(PackageReference->'$(FileName)')
-
Use
Identity
, notFilename
.Filename
excludes what appears to be a file extension. For 'Microsoft.NET.Test.Sdk',Filename
is 'Microsoft.NET.Test'.Identity
is the value from theInclude
. -
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 PackageReference
s
"'@(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" Condition="!$(IsTestProject)">
...
The Condition
is tested before running the Target
s in the DependsOnTargets
. This may seem like a 'quirk' but is actually very useful behavior.
There are two ways around this.
- Move the
Condition
to an element within theTarget
.
<Target Name="ValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject">
<Error Condition="!$(IsTestProject) and '$(EnableDynamicLoading)' != 'true'" .../>
...
- For more complex situations, create an 'orchestrator'
Target
that arranges the order of otherTarget
s.
<Target Name="RunValidateModuleProjectProperties" BeforeTargets="BeforeBuild" DependsOnTargets="DefineIsTestProject;ValidateModuleProjectProperties"/>
<Target Name="ValidateModuleProjectProperties" Condition="!$(IsTestProject)">
...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论