.NET: Make GitHub.Copilot.SDK build targets reach transitive consumers (#6455) (#6457)

* .NET: Make GitHub.Copilot.SDK build targets reach transitive consumers (#6455)

Microsoft.Agents.AI.GitHub.Copilot now ships a buildTransitive/ bridge so
consumers who only reference this package (the normal use case) get the
GitHub.Copilot.SDK's CLI binary-download MSBuild targets executed at build
time. Without this, the SDK shipped its targets under build/ which NuGet
only auto-imports for projects with a direct PackageReference to the SDK,
so consumers of the adapter package got only the managed .dll, no
copilot.exe in their output, and a runtime InvalidOperationException on
the first RunAsync.

The bridge consists of two files under buildTransitive/:

* Microsoft.Agents.AI.GitHub.Copilot.props is generated at this package's
  pack time and pins the SDK version (from PackageVersion items in
  Directory.Packages.props) into _MicrosoftAgentsAICopilotSdkVersion.

* Microsoft.Agents.AI.GitHub.Copilot.targets is static and imports the
  SDK's own build/GitHub.Copilot.SDK.targets from the NuGet cache using
  the pinned version. The version-pin condition no-ops gracefully if the
  resolved SDK differs from what was baked in (e.g. consumer overrides
  the SDK version directly), so this is purely additive.

Verified by packing locally, restoring from a flat local feed, and
building a transitive-only consumer (PackageReference to MAF only, no
direct SDK ref). copilot.exe lands at bin/{cfg}/{tfm}/runtimes/{rid}/
native/copilot.exe as expected, matching the path the SDK's runtime
CopilotClient looks at.

Fixes #6455

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address Copilot review feedback (#6457)

- buildTransitive/.targets: compute the full SDK targets path with a single
  Path.Combine call into one property (_MicrosoftAgentsAICopilotSdkTargetsPath),
  used in both Project= and Exists() — no more split between Path.Combine for
  the directory and inline / separator for the file name.

- Split the version-defaulting Condition between the two files: the generated
  .props now just bakes the packaged SDK version into a dedicated property
  (_MicrosoftAgentsAICopilotSdkPackagedVersion), and the static .targets file
  is the single place that defaults _MicrosoftAgentsAICopilotSdkVersion to it.
  Removes the need for any MSBuild escape gymnastics in the pack-time string
  construction, and keeps the consumer override path the same.

- _GenerateBuildTransitiveProps now hangs off public BeforeTargets (Build, Pack)
  in addition to _GetPackageFiles, so the file is generated even without a
  full pack, and we're not solely dependent on an underscore-prefixed internal
  target. The <None Pack=true /> items live in a top-level ItemGroup so they
  are collected at evaluation time instead of being added from inside the
  Target.

End-to-end retested with a transitive-only consumer (PackageReference to MAF
only, no direct GitHub.Copilot.SDK ref): copilot.exe lands at
bin/Debug/net10.0/runtimes/win-x64/native/copilot.exe (141.8 MB) as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Tamir Dresher <tamirdresher@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Tamir Dresher
2026-06-10 21:07:18 +03:00
committed by GitHub
Unverified
parent dd29f9aa65
commit 60cc5ee4e4
3 changed files with 85 additions and 0 deletions
@@ -32,4 +32,52 @@
<Description>Provides Microsoft Agent Framework support for GitHub Copilot SDK.</Description>
</PropertyGroup>
<!--
buildTransitive bridge for GitHub.Copilot.SDK's CLI binary-download targets.
GitHub.Copilot.SDK ships its CLI download targets under build/, which NuGet
only auto-imports for projects with a DIRECT PackageReference to the SDK.
Consumers of this package (who reference Microsoft.Agents.AI.GitHub.Copilot
instead of GitHub.Copilot.SDK directly) would otherwise get only the managed
adapter .dll, no copilot.exe in their output, and a runtime failure at the
first RunAsync call.
The targets file in buildTransitive/ is static and contains all of the
Condition / Import logic. The .props file generated here just bakes the
SDK version this package was built against into a dedicated property
($(_MicrosoftAgentsAICopilotSdkPackagedVersion)) — no Condition or
inner $(...) reference in the generated file, so we avoid any MSBuild
escape gymnastics during string construction. The static targets file
then defaults $(_MicrosoftAgentsAICopilotSdkVersion) to the packaged
version unless the consumer overrides it.
-->
<ItemGroup>
<None Include="buildTransitive\Microsoft.Agents.AI.GitHub.Copilot.targets" Pack="true" PackagePath="buildTransitive\" />
<None Include="buildTransitive\Microsoft.Agents.AI.GitHub.Copilot.props" Pack="true" PackagePath="buildTransitive\" />
</ItemGroup>
<Target Name="_GenerateBuildTransitiveProps" BeforeTargets="Build;Pack;_GetPackageFiles">
<ItemGroup>
<_CopilotSdkPackageVersion Include="@(PackageVersion)" Condition="'%(Identity)' == 'GitHub.Copilot.SDK'" />
</ItemGroup>
<PropertyGroup>
<_CopilotSdkResolvedVersion>@(_CopilotSdkPackageVersion->'%(Version)')</_CopilotSdkResolvedVersion>
</PropertyGroup>
<Error Condition="'$(_CopilotSdkResolvedVersion)' == ''"
Text="Could not resolve GitHub.Copilot.SDK version from PackageVersion items. Ensure the central package version is declared in Directory.Packages.props." />
<PropertyGroup>
<_BuildTransitivePropsContent>
<![CDATA[<Project>
<PropertyGroup>
<_MicrosoftAgentsAICopilotSdkPackagedVersion>$(_CopilotSdkResolvedVersion)</_MicrosoftAgentsAICopilotSdkPackagedVersion>
</PropertyGroup>
</Project>]]>
</_BuildTransitivePropsContent>
</PropertyGroup>
<WriteLinesToFile File="$(MSBuildThisFileDirectory)buildTransitive\Microsoft.Agents.AI.GitHub.Copilot.props"
Lines="$(_BuildTransitivePropsContent)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
</Target>
</Project>
@@ -0,0 +1,3 @@
# Auto-generated at pack time by _GenerateBuildTransitiveProps in the csproj.
Microsoft.Agents.AI.GitHub.Copilot.props
@@ -0,0 +1,34 @@
<Project>
<!--
Bridge GitHub.Copilot.SDK's build/ targets to transitive consumers.
GitHub.Copilot.SDK ships its CLI download / binary-copy MSBuild targets under
build/, which means they only auto-import for projects with a DIRECT
PackageReference to GitHub.Copilot.SDK. Consumers of this package
(Microsoft.Agents.AI.GitHub.Copilot) would otherwise get only the managed
adapter .dll, no copilot CLI binary in their output, and the SDK would fail
at runtime with:
Copilot CLI not found at 'bin/{config}/{tfm}/runtimes/{rid}/native/copilot.exe'
This file ships under buildTransitive/ so NuGet auto-imports it for every
transitive consumer, locates the SDK in the NuGet package cache, and imports
the SDK's build/ targets so the CLI binary gets downloaded and copied to the
consumer's output as expected.
The companion .props file is generated at this package's pack time and sets
$(_MicrosoftAgentsAICopilotSdkPackagedVersion) to the SDK version this
package was built against. The Condition below uses that as the default for
$(_MicrosoftAgentsAICopilotSdkVersion), so consumers may override the SDK
version path by setting $(_MicrosoftAgentsAICopilotSdkVersion) before this
file is imported. Consumers may also opt out of the binary download via the
SDK's own $(CopilotSkipCliDownload)=true, which the SDK targets honor.
-->
<PropertyGroup>
<_MicrosoftAgentsAICopilotSdkVersion Condition="'$(_MicrosoftAgentsAICopilotSdkVersion)' == ''">$(_MicrosoftAgentsAICopilotSdkPackagedVersion)</_MicrosoftAgentsAICopilotSdkVersion>
<_MicrosoftAgentsAICopilotSdkTargetsPath Condition="'$(_MicrosoftAgentsAICopilotSdkVersion)' != ''">$([System.IO.Path]::Combine('$(NuGetPackageRoot)', 'github.copilot.sdk', '$(_MicrosoftAgentsAICopilotSdkVersion)', 'build', 'GitHub.Copilot.SDK.targets'))</_MicrosoftAgentsAICopilotSdkTargetsPath>
</PropertyGroup>
<Import Project="$(_MicrosoftAgentsAICopilotSdkTargetsPath)"
Condition="'$(_MicrosoftAgentsAICopilotSdkTargetsPath)' != '' And Exists('$(_MicrosoftAgentsAICopilotSdkTargetsPath)')" />
</Project>