Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post build event is run an extra time with empty values when multi-targeting #11277

Open
rwb196884 opened this issue Jan 14, 2025 · 3 comments
Open
Labels
Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) Priority:2 Work that is important, but not critical for the release triaged

Comments

@rwb196884
Copy link

Issue Description

I've added a post build event to my csproj to sign DLLs (getting this to run in the Azure DevOps pipeline was hell -- took a day.)

  <Target Name="AzureSignTool" AfterTargets="Build" Condition="'$(TargetFileName)' != ''">
    <Message Importance="High" Text="Running azuresigntool for $(TargetFileName)." />
    <Exec Command="azuresigntool sign --azure-key-vault-tenant-id &quot;$(AzureSignTool_TenantId)&quot; -kvu $(AzureSignTool_KeyVaultUrl) -kvi $(AzureSignTool_ClientId) -kvs $(AzureSignTool_ClientSecret) -kvc CodeSigningCert &quot;$(ProjectDir)$(OutDir)$(TargetFileName)&quot;" />
  </Target>

The Condition is required beause the target is run for ever <Frameworks and again for the empty string.

The problem happens when you use <TargetFrameworks instead of <TargetFramework. Example attached.

Steps to Reproduce

ConsoleAppPostBuildEvent.zip

Expected Behavior

Don't run the post build event an additional time.

Actual Behavior

Runs post build event once for every frameowrk and agan with empty parameter values.

Analysis

No response

Versions & Configurations

No response

@maridematte maridematte added Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) Priority:2 Work that is important, but not critical for the release triaged labels Jan 14, 2025
@maridematte
Copy link
Contributor

This is expected behaviour, and we need to improve the documentation around it. But how it works, when you have TargetFrameworks (plural) defined, it adds another invisible build. Informally we call it the "outer-build". This allows us to prep all builds with the correct targetframeworks and have one nice call all around.

This extra post build that you are seeing is from the outer build, that is also used in some cases, and removal would probably break a lot of builds.

@baronfel
Copy link
Member

@maridematte we should probably add some documentation about the general pattern to https://learn.microsoft.com/en-us/dotnet/standard/frameworks - I can log a docs bug for that. I don't see that it's been specified anywhere.

@baronfel
Copy link
Member

@rwb196884 for reference, here is the main bit of code relating to multi-targeted builds:

<!--
============================================================
DispatchToInnerBuilds
Builds this project with /t:$(InnerTarget) /p:TargetFramework=X for each
value X in $(TargetFrameworks)
[IN]
$(TargetFrameworks) - Semicolon delimited list of target frameworks.
$(InnerTargets) - The targets to build for each target framework
[OUT]
@(InnerOutput) - The combined output items of the inner targets across
all target frameworks..
============================================================
-->
<Target Name="DispatchToInnerBuilds"
DependsOnTargets="_ComputeTargetFrameworkItems"
Returns="@(InnerOutput)">
<!-- If this logic is changed, also update Clean -->
<MSBuild Projects="@(_InnerBuildProjects)"
Condition="'@(_InnerBuildProjects)' != '' "
Targets="$(InnerTargets)"
BuildInParallel="$(BuildInParallel)">
<Output ItemName="InnerOutput" TaskParameter="TargetOutputs" />
</MSBuild>
</Target>
<!--
============================================================
Build
Cross-targeting version of Build.
[IN]
$(TargetFrameworks) - Semicolon delimited list of target frameworks.
$(InnerTargets) - The targets to build for each target framework. Defaults
to 'Build' if unset, but allows override to support
`msbuild /p:InnerTargets=X;Y;Z` which will build X, Y,
and Z targets for each target framework.
[OUT]
@(InnerOutput) - The combined output items of the inner targets across
all builds.
============================================================
-->
<Target Name="Build" DependsOnTargets="_SetBuildInnerTarget;DispatchToInnerBuilds" />
<Target Name="_SetBuildInnerTarget" Returns="@(InnerOutput)">
<PropertyGroup Condition="'$(InnerTargets)' == ''">
<InnerTargets>Build</InnerTargets>
</PropertyGroup>
</Target>

When building, as @maridematte said you should expect more than one build of the project to occur:

  • one outer build to discover the values of TargetFrameworks
  • one 'inner' build per individual value of TargetFrameworks

As a result, it's very important to make sure that targets run in the correct context - this is why NuGet packages that ship MSBuild targets have both the build and buildMultitargeting directories - this enforces a separation between targets that can run in single-TFM and 'outer' builds vs those that should only run in 'inner' builds.

If you'd like to ensure that your target only runs when a single TFM has been specified, you probably need to add an additional check to your Condition like '$(IsCrossTargetingBuild)' != 'true'. IsCrossTargetingBuild is set to true if TargetFrameworks is present and no single TargetFramework has been chosen - so it's true in the outer build and unset/false-y in the inner builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) Priority:2 Work that is important, but not critical for the release triaged
Projects
None yet
Development

No branches or pull requests

3 participants