Reputation: 45
I have tried to read in Microsoft documentations as well in Nuget and Dotnet and was unable to understand the corresponding behavior.
When working on a C# project along with .csproj
I have encountered the following,
Here is an example of what I want to achieve:
I am working for instance with the following .csproj
file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Authors>Confluent Inc.;Andreas Heider</Authors>
<Description>Confluent's .NET Client for Apache Kafka</Description>
<Copyright>Copyright 2016-2020 Confluent Inc., Andreas Heider</Copyright>
<PackageProjectUrl>https://github.com/confluentinc/confluent-kafka-dotnet/</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/LICENSE</PackageLicenseUrl>
<PackageIconUrl>https://raw.githubusercontent.com/confluentinc/confluent-kafka-dotnet/master/confluent_logo.png</PackageIconUrl>
<PackageReleaseNotes>https://github.com/confluentinc/confluent-kafka-dotnet/releases</PackageReleaseNotes>
<PackageTags>Kafka;Confluent;librdkafka</PackageTags>
<PackageId>Confluent.Kafka</PackageId>
<Title>Confluent.Kafka</Title>
<AssemblyName>Confluent.Kafka</AssemblyName>
<VersionPrefix>1.4.3</VersionPrefix>
<TargetFrameworks>net45;net46;netcoreapp2.1;netstandard1.3;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Confluent.Kafka.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="librdkafka.redist" Version="1.4.2">
<PrivateAssets Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">None</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Memory" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="PeterO.Cbor" Version="3.5.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Console" Version="4.3.0" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.0" />
<PackageReference Include="System.Threading" Version="4.3.0" />
</ItemGroup>
</Project>
As you can see in the last ItemGroup
there is a Condition=" '$(TargetFramework)' == 'netstandard1.3'
,
what I am trying to figure out is from where dotnet takes this argument $(TargetFramework)
from.
By the way, it seems to be variable notation $(SomeVariable)
(not only TargetFramework
) and it would be great for me to understand from where and how these variables are being pulled from.
Any leads?
Thanks!
Upvotes: 1
Views: 292
Reputation: 100543
As there is already a very detailed answer on some mechanics, here are a few helpful pieces of information to know that can help detail the understanding of the behavior:
It is actually syntactical sugar for <Import>
statements at the top and bottom ends of your project content. This means that logic by the SDK (and its deeper imports) can both set defaults that you can use in your project (automatic Sdk.props
import at the top) and process values you set in the project (automatic Sdk.targets
import at the very bottom).
<PropertyGroup>
elemts are processed before <ItemGroup>
elements.MSBuild evaluates static content (i.e. not inside a <Target>
) in a very specific order: Property Groups and Imports then Item Definition Groups and then Item Groups.
This means you can use $(FooBar)
in an <ItemGroup>
at the very top even if the <PropertyGroup>
defining <FooBar>
i at the very bottom.
The SDK itself uses this behavior - your $(TargetFramework)
(singular!, if set. more on that later) is used in some property groups in the imported SDK targets to set TargetFrameworkIdentifier
, TargetFrameworkVersion
and TargetFrameworkMoniker
- here's the code.
When the processing of <PropertyGroup>
s is complete, this means that on the pass for <ItemGroup>
elements, you can use $(TargetFrameworkIdentifier)
.
A similar mechansim is used for some item groups that the sdk has before your project content that can be controlled by properties set in the project (DefaultItemExcludes
, EnableDefaultCompileItems
etc).
TargetFrameworks
(plural) projects actually build multiple times.If a project has no TargetFramework
but a TargetFrameworks
(plural) property, it causes multiple sub-builds for which the TargetFramework
property will be set. This means that everything is evaluated once for an "outer" build and then once per target framework for "inner builds". Here's the code
Upvotes: 2
Reputation: 6610
$(SomeVariable)
is the syntax for resolving an MSBuild property. These are key-value pairs, which can be defined by environment variables, in arguments to MSBuild, by a <PropertyGroup>
elements within an MSBuild file, and by MSBuild targets using the CreateProperty
task.
TargetFramework
is somewhat of a special property. If the project only builds for one target framework, then the property is set explicitly in the project file, e.g.:
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
In your case, your project can be built for multiple frameworks, as specified in the TargetFrameworks
property (notice the plural), e.g.:
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net462</TargetFrameworks>
</PropertyGroup>
When you build this project with e.g. dotnet
, it actually builds the project multiple times, once for each target framework, by essentially setting the TargetFramework
property via argument when calling MSBuild.
If you want to see behind the magic a bit, here are two ways to get very verbose information on what MSBuild is doing:
Use the -pp
argument to have MSBuild resolve all the magic .NET Core-ish stuff, as well as all imported project files, into a single project file for you to inspect. This won't actually run the build.
Use the -bl
argument to run the build, but log everything MSBuild does in a binary format. You can then view the .binlog
file using this GUI.
Additional resources for MSBuild:
Upvotes: 1