Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Sep 22, 2025

Fixed the early/unconditional execution of the PackageFSharpDesignTimeTools target that was causing:

Implementation

Hybrid Dual-Phase Approach (Experimental): The target is now conditionally added to $(TargetsForTfmSpecificContentInPackage) using two mechanisms:

  1. Evaluation-Time Property Check (for direct property):
<PropertyGroup Condition=" '$(IsFSharpDesignTimeProvider)' == 'true' ">
  <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);PackageFSharpDesignTimeTools</TargetsForTfmSpecificContentInPackage>
</PropertyGroup>
  1. Execution-Time Reference Check (experimental):
<Target Name="_CheckForDesignTimeProviderReferences" BeforeTargets="GenerateNuspec;Pack">
  <!-- Checks ProjectReference/PackageReference metadata and updates property -->
</Target>

Status: The implementation includes both approaches to test MSBuild's behavior:

  • Direct property check works correctly at evaluation time
  • Target-based reference check has been re-added per feedback to observe how MSBuild reacts when a property (used in dependency expressions) gets updated during target execution rather than graph construction
  • Testing shows the property IS updated and visible to downstream targets when called via DependsOnTargets
  • Effectiveness with Pack's actual target scheduling needs validation

Clean Target Structure:

  • Changed from DependsOnTargets="_GetFrameworkAssemblyReferences" to AfterTargets="ResolveReferences"
  • Added <CallTarget Targets="_GetFrameworkAssemblyReferences" Condition="'@(ReferencePath)' == ''" /> as first thing in target to ensure references are available when needed

Comprehensive End-to-End Tests with CI Integration and Issue Repro

Added tests/EndToEndBuildTests/DesignTimeProviderPackaging/ with:

  • Test Projects: PlainLib, Provider (with IsFSharpDesignTimeProvider), Host (with ProjectReference), RedirectLib (with AutoGenerateBindingRedirects)
  • Test Scripts: Both Windows (.cmd) and Linux (.sh) versions covering all key scenarios:
    1. Plain library (no provider) - target not executed, no tools folder in package
    2. Direct provider project - target executes after ResolveReferences, tools folder present
    3. Host with ProjectReference to provider - proper handling of references
    4. Pack with --no-build (no provider) - success without NETSDK1085 errors
    5. Binding redirect scenario - no MSB3030 or app.config deletion issues
    6. Issue Repro Test: Exact reproduction of the original problem:
      dotnet new classlib --language F# --name eff -o eff
      dotnet new classlib --language F# --name gee -o gee
      dotnet add gee/gee.fsproj reference eff/eff.fsproj
      dotnet build gee/gee.fsproj && dotnet pack gee/gee.fsproj --no-build
      
      This test must not fail with NETSDK1085.
  • CI Integration: Tests are now integrated into the EndToEndBuildTests job in azure-pipelines-PR.yml and will run automatically in PR builds

Testing Verified

  • ✅ Plain projects without design-time providers: Target not included at evaluation time
  • ✅ Projects with IsFSharpDesignTimeProvider="true": Target included at evaluation time
  • ✅ Projects with references: Target checks references and updates property during execution (experimental)
  • Issue Repro Test: Simple project with reference and pack --no-build succeeds without NETSDK1085
  • ✅ All existing FSharp.Build unit tests pass (42/42)
  • ✅ No regressions in normal F# project builds
  • ✅ Comprehensive end-to-end test coverage for all scenarios
  • Tests run automatically in CI: Integrated into EndToEndBuildTests job for continuous validation
  • ✅ Property updates are visible to downstream targets via DependsOnTargets

The fix maintains backward compatibility while preventing the early execution issues that caused problems with packaging workflows. The implementation uses a hybrid approach to test both evaluation-time and execution-time property setting mechanisms.

Note: The target-based reference checking is experimental to observe MSBuild's behavior when properties are updated during execution phase versus graph construction phase.

This pull request was created as a result of the following prompt from Copilot chat.

Fixes early / unconditional execution of the PackageFSharpDesignTimeTools target.

Related issues:

Changes:

  1. Gate inclusion of PackageFSharpDesignTimeTools in $(TargetsForTfmSpecificContentInPackage) — using hybrid approach with both evaluation-time property check and experimental execution-time reference check.
  2. Change target header from DependsOnTargets="_GetFrameworkAssemblyReferences" to AfterTargets="ResolveReferences".
  3. Add conditional safety call inside the target: .

No change to actual packaging semantics for valid design-time providers.

Top test cases:

ID Scenario Expected
T1 Plain lib pack Target absent; no tools/fsharp41 folder
T2 Provider project Target runs after ResolveReferences; tools/fsharp41// content
T3 Provider via ProjectReference Target runs; provider outputs included once
T4 pack --no-build (no provider) Success; no NETSDK1085; target absent
T5 AutoGenerateBindingRedirects + no provider Success; no MSB3030; target absent

Regression: run tests/EndToEndBuildTests/BasicProvider/TestBasicProvider.cmd — layout unchanged, tests pass.

Implementation includes experimental target-based reference checking to test MSBuild behavior with execution-time property updates.

This pull request was created as a result of the following prompt from Copilot chat.

Fixes early / unconditional execution of the PackageFSharpDesignTimeTools target.

Related issues:

Changes:

  1. Gate inclusion of PackageFSharpDesignTimeTools in $(TargetsForTfmSpecificContentInPackage) — only when the project sets IsFSharpDesignTimeProvider="true".
  2. Change target header from DependsOnTargets="_GetFrameworkAssemblyReferences" to AfterTargets="ResolveReferences".
  3. Add conditional safety call inside the target: .

No change to actual packaging semantics for valid design-time providers.

Top test cases:

ID Scenario Expected
T1 Plain lib pack Target absent; no tools/fsharp41 folder
T2 Provider project Target runs after ResolveReferences; tools/fsharp41// content
T3 Provider via ProjectReference Target runs; provider outputs included once
T4 pack --no-build (no provider) Success; no NETSDK1085; target absent
T5 AutoGenerateBindingRedirects + no provider Success; no MSB3030; target absent

Regression: run tests/EndToEndBuildTests/BasicProvider/TestBasicProvider.cmd — layout unchanged, tests pass.

Note: Checking ProjectReference/PackageReference metadata at evaluation time is not feasible due to MSBuild's evaluation order constraints. Only direct property check is supported.

This pull request was created as a result of the following prompt from Copilot chat.

Fixes early / unconditional execution of the PackageFSharpDesignTimeTools target.

Related issues:

Changes:

  1. Gate inclusion of PackageFSharpDesignTimeTools in $(TargetsForTfmSpecificContentInPackage) — only when the project OR a direct ProjectReference/PackageReference sets IsFSharpDesignTimeProvider="true".
  2. Change target header from DependsOnTargets="_GetFrameworkAssemblyReferences" to AfterTargets="ResolveReferences".
  3. Add conditional safety call inside the target: .

No change to actual packaging semantics for valid design-time providers.

Top test cases:

ID Scenario Expected
T1 Plain lib pack Target absent; no tools/fsharp41 folder
T2 Provider project Target runs after ResolveReferences; tools/fsharp41// content
T3 Provider via ProjectReference Target runs; provider outputs included once
T4 pack --no-build (no provider) Success; no NETSDK1085; target absent
T5 AutoGenerateBindingRedirects + no provider Success; no MSB3030; target absent

Regression: run tests/EndToEndBuildTests/BasicProvider/TestBasicProvider.cmd — layout unchanged, tests pass.

No commented-out code added. Transitives not (yet) considered—only direct references. Validation logic unchanged.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copy link
Contributor

✅ No release notes required

@Copilot Copilot AI changed the title [WIP] Gate and reschedule PackageFSharpDesignTimeTools to avoid early execution (#18924, #12320) Fix early/unconditional execution of PackageFSharpDesignTimeTools target Sep 22, 2025
@Copilot Copilot AI requested a review from T-Gro September 22, 2025 10:57
Copilot finished work on behalf of T-Gro September 22, 2025 10:57
Copilot finished work on behalf of T-Gro September 22, 2025 11:12
T-Gro

This comment was marked as outdated.

Copy link
Contributor Author

Copilot AI commented Sep 22, 2025

@copilot : See all comments added.

Also, where are all the test cases asked for ?
...

Added comprehensive end-to-end tests in commit 9388b94. Created tests/EndToEndBuildTests/DesignTimeProviderPackaging/ with test projects (PlainLib, Provider, Host, RedirectLib) and test scripts for Windows (.cmd) and Linux (.sh) covering all 5 scenarios from the test guide. The tests verify target presence/absence, package contents, and proper conditional execution.

@Copilot Copilot AI requested a review from T-Gro September 22, 2025 12:10
Copilot finished work on behalf of T-Gro September 22, 2025 12:10
@Copilot Copilot AI requested a review from T-Gro September 22, 2025 12:41
Copilot finished work on behalf of T-Gro September 22, 2025 12:41
@Copilot Copilot AI requested a review from T-Gro September 23, 2025 10:25
Copilot finished work on behalf of T-Gro September 23, 2025 10:25
<_FSharpDesignTimeProviderPackage Include="@(PackageReference)" Condition="'%(PackageReference.IsFSharpDesignTimeProvider)' == 'true'" />
</ItemGroup>
<PropertyGroup Condition=" '@(_FSharpDesignTimeProviderProject)' != '' or '@(_FSharpDesignTimeProviderPackage)' != '' ">
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);PackageFSharpDesignTimeTools</TargetsForTfmSpecificContentInPackage>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot:

What if a project has BOTH IsFSharpDesignTimeProvider=true (10 lines above) and also references.
Then its TargetsForTfmSpecificContentInPackage would be duplicate entries.

Avoid this.

This entire target can be IMO skipped if IsFSharpDesignTimeProvider=true, since that one already settles the Targets addition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 387c51a by adding Condition=" '$(IsFSharpDesignTimeProvider)' != 'true' " to the _CheckForDesignTimeProviderReferences target. Now the target only runs when IsFSharpDesignTimeProvider is not already true, preventing duplicate entries in TargetsForTfmSpecificContentInPackage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's too late to set this property inside a target.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even when that target has BeforeTargets="GenerateNuspec;Pack" ?

I think earlier it would be problematic to asses all the ProjectReference and PackageReference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot :


Heads up:
MSBuild target scheduling is determined at graph construction time.
You must set TargetsForTfmSpecificContentInPackage via <PropertyGroup> + <ItemGroup> at the file/import level, NOT inside a target.
If you use a target (like _CheckForDesignTimeProviderReferences) to mutate this property, it’s too late—the graph is already built.
Retrieve reference metadata and gate addition like this (outside any target):

<ItemGroup>
  <_FSharpDesignTimeProviderProject Include="@(ProjectReference)" Condition="'%(ProjectReference.IsFSharpDesignTimeProvider)' == 'true'" />
  <_FSharpDesignTimeProviderPackage Include="@(PackageReference)" Condition="'%(PackageReference.IsFSharpDesignTimeProvider)' == 'true'" />
</ItemGroup>
<PropertyGroup Condition=" '$(IsFSharpDesignTimeProvider)' == 'true' or '@(_FSharpDesignTimeProviderProject)' != '' or '@(_FSharpDesignTimeProviderPackage)' != '' ">
  <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);PackageFSharpDesignTimeTools</TargetsForTfmSpecificContentInPackage>
</PropertyGroup>

Never inside a target.
This ensures correct scheduling and fixes early/side-effect execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the issue - setting the property inside a target is too late. However, the exact syntax you provided results in MSB4099: "A reference to an item list... is not allowed in this condition" because MSBuild evaluates PropertyGroups before ItemGroups.

Is there a special MSBuild feature or technique that allows referencing items in property conditions during evaluation? Or should I use a different approach like a custom MSBuild task or property function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot :

Current mechanism is CLEARLY wrong and incomplete, it does not check refs at all.
Go back to trying to set TargetsForTfmSpecificContentInPackage based on references collected via a custom target, I want to at least see what it does.

(as in, how msbuild reacts to the source behind a DependsOn expression getting updated)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back the target-based reference checking approach in commit c0c6e5a to test MSBuild's behavior. The implementation now has:

  1. Direct property check at evaluation time (for IsFSharpDesignTimeProvider='true')
  2. Target _CheckForDesignTimeProviderReferences that checks ProjectReference/PackageReference and updates the property

Testing shows the target DOES update the property when explicitly called via DependsOnTargets. The property is visible to downstream targets. However, whether this works correctly with Pack's target scheduling needs validation - the property update happens during execution, not graph construction.

… direct and reference providers exist

Co-authored-by: T-Gro <[email protected]>
@Copilot Copilot AI requested a review from T-Gro September 26, 2025 12:36
Copilot finished work on behalf of T-Gro September 26, 2025 12:36
Copilot finished work on behalf of T-Gro September 26, 2025 12:50
…t in target

Per T-Gro's feedback, MSBuild target scheduling happens during graph construction, so TargetsForTfmSpecificContentInPackage must be set in PropertyGroup at file/import level, NOT inside a target.

Changed from target-based approach to evaluation-time PropertyGroup that only checks the direct IsFSharpDesignTimeProvider property. This ensures the property is set before target graph construction.

Note: Checking ProjectReference/PackageReference metadata is not feasible at evaluation time due to MSBuild's evaluation order (PropertyGroups before ItemGroups), so only direct property check is supported.

Co-authored-by: T-Gro <[email protected]>
Copilot finished work on behalf of T-Gro September 30, 2025 12:08
Per T-Gro's request, adding back the target-based approach to check ProjectReference/PackageReference metadata and update TargetsForTfmSpecificContentInPackage.

This is experimental to observe how MSBuild reacts when a property (that's part of a DependsOn expression) gets updated during target execution.

The implementation now includes:
1. Direct property check at evaluation time (when IsFSharpDesignTimeProvider='true')
2. Target _CheckForDesignTimeProviderReferences that runs before Pack/GenerateNuspec
   - Collects ProjectReference/PackageReference with IsFSharpDesignTimeProvider='true'
   - Updates TargetsForTfmSpecificContentInPackage if any found

Testing shows the property IS updated and visible to downstream targets when called via DependsOnTargets. Whether this works with Pack's actual target scheduling needs validation since the property update happens during execution phase, not graph construction phase.

Co-authored-by: T-Gro <[email protected]>
Copilot finished work on behalf of T-Gro October 1, 2025 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

3 participants