Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c99316b
Initial plan
Copilot Jan 14, 2026
9b90613
Make win-x64 native DLLs conditional on Windows runtime
Copilot Jan 14, 2026
9c8e4f5
Merge latest changes from master
Copilot Jan 14, 2026
c052b0a
Add unit tests for Microsoft.Azure.Cosmos.targets conditional DLL inc…
Copilot Jan 14, 2026
6c8d151
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Jan 14, 2026
5c932d1
Replace XML parsing tests with integration tests for publish validation
Copilot Jan 14, 2026
c2460bf
Add CosmosClient instantiation in test projects to ensure SDK usage
Copilot Jan 15, 2026
b65f049
Add command tracing to publish tests for better diagnostics
Copilot Jan 15, 2026
6ccd8be
Fixing the build errors
kirankumarkolli Jan 15, 2026
d690b7e
Fix .NET 6 compatibility and update condition to include empty Runtim…
Copilot Jan 15, 2026
4c72670
Restore warning header, add documentation, and refactor tests with Da…
Copilot Jan 15, 2026
5c3efb0
Add null check for Process.Start and fix stylecop warnings with docum…
Copilot Jan 15, 2026
d542a9f
Small styleop fixes
kirankumarkolli Jan 15, 2026
f1ffd62
Fix process handling and add DoNotParallelize attribute to tests
Copilot Jan 15, 2026
7e9abb5
Remove unreachable code and add null-forgiving operator
Copilot Jan 15, 2026
6f569c9
Fix CosmosTargetsPublishTests to use local NuGet package
kirankumarkolli Jan 28, 2026
08b2ee0
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Jan 28, 2026
56667b7
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Apr 1, 2026
74befb4
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Apr 1, 2026
74fbc6e
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kundadebdatta Apr 4, 2026
b96bb10
Address PR review comments: fix deadlock, add no-RID test, update TFM
kirankumarkolli Apr 11, 2026
6286b22
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Apr 11, 2026
2f85656
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Apr 21, 2026
2c40593
Revert documentation changes to simpler approved version
Copilot Apr 21, 2026
8d710e9
Restore original indentation for msvcp140, vcruntime140, vcruntime140…
Copilot Apr 21, 2026
ef37530
Address PR feedback: assert empty errors, simplify Program.cs, inline…
Copilot Apr 22, 2026
eb9e9a4
addressing the comment part
kirankumarkolli Apr 22, 2026
ef8b1b7
Tests: Refactors CosmosTargetsPublishTests to use async/await
kirankumarkolli Apr 22, 2026
bc56530
Tests: Adds publish output validation to prevent vacuous test passes
kirankumarkolli Apr 22, 2026
1608b4f
Tests: Refactors process management into RunDotnetCommandAsync helper
kirankumarkolli Apr 22, 2026
ae02b3e
Tests: Refactors CreateTestProject and PublishProjectAsync into singl…
kirankumarkolli Apr 22, 2026
7afaa25
Merge branch 'master' into copilot/fix-unconditional-dll-copying
kirankumarkolli Apr 22, 2026
ce0f87f
Renaming the file
kirankumarkolli Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.targets
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,38 @@ Copyright (C) Microsoft Corporation. All rights reserved.
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemGroup>
<!-- Only include Windows native DLLs when:
1. RuntimeIdentifier is empty (regular build without RID), OR
2. RuntimeIdentifier is specified AND starts with 'win'

Note: The 'win' prefix filter is used for backward compatibility and may copy
DLLs for win-x86, win-x64, win-arm, win-arm64, etc. and ServiceInteropWrapper
will attempt to load the correct one based on the actual architecture of the
machine at runtime.

Currently on win-x64 only, but this allows for future expansion for win-arm64.
-->
<ItemGroup Condition="'$(RuntimeIdentifier)' == '' OR $(RuntimeIdentifier.StartsWith('win'))">
Comment thread
kirankumarkolli marked this conversation as resolved.
<ContentWithTargetPath Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\Microsoft.Azure.Cosmos.ServiceInterop.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Microsoft.Azure.Cosmos.ServiceInterop.dll</TargetPath>
<Visible>False</Visible>
</ContentWithTargetPath>
</ItemGroup>
Comment thread
kirankumarkolli marked this conversation as resolved.

<ItemGroup>
<ContentWithTargetPath Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\Cosmos.CRTCompat.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Cosmos.CRTCompat.dll</TargetPath>
<Visible>False</Visible>
</ContentWithTargetPath>
</ItemGroup>

<ItemGroup>
<ContentWithTargetPath Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\msvcp140.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>msvcp140.dll</TargetPath>
<Visible>False</Visible>
</ContentWithTargetPath>
</ItemGroup>

<ItemGroup>
<ContentWithTargetPath Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\vcruntime140.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>vcruntime140.dll</TargetPath>
<Visible>False</Visible>
</ContentWithTargetPath>
</ItemGroup>

<ItemGroup>
<ContentWithTargetPath Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\vcruntime140_1.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>vcruntime140_1.dll</TargetPath>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Tests.MSBuild
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// Integration tests that verify Windows native DLLs are only copied when publishing
/// for Windows RuntimeIdentifiers, and not for Linux/macOS targets.
/// These tests pack the SDK into a local NuGet package to properly test the .targets file behavior.
/// </summary>
[TestClass]
Comment thread
kirankumarkolli marked this conversation as resolved.
[TestCategory("LongRunning")]
public class CosmosTargetsInteropPublishTests
{
private static string testProjectsRoot;
private static string localNugetPackagePath;
private static string packageVersion;
private static readonly string[] WindowsNativeDlls = new[]
{
"Microsoft.Azure.Cosmos.ServiceInterop.dll",
"Cosmos.CRTCompat.dll",
"msvcp140.dll",
"vcruntime140.dll",
"vcruntime140_1.dll"
};

[ClassInitialize]
public static async Task ClassInitialize(TestContext _)
{
testProjectsRoot = Path.Combine(Path.GetTempPath(), "CosmosTargetsTests_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(testProjectsRoot);

// Create local NuGet package from the SDK
await CreateLocalNuGetPackageAsync();
}

[ClassCleanup]
public static void ClassCleanup()
{
if (Directory.Exists(testProjectsRoot))
{
try
{
Directory.Delete(testProjectsRoot, recursive: true);
}
catch
{
// Ignore cleanup errors
}
}
}

/// <summary>
/// Tests that Windows native DLLs are not copied when publishing for non-Windows platforms.
/// </summary>
/// <param name="runtimeIdentifier">The runtime identifier to test (e.g., linux-x64, osx-x64).</param>
[TestMethod]
[DataRow("linux-x64")]
[DataRow("linux-arm64")]
[DataRow("osx-x64")]
Comment thread
kirankumarkolli marked this conversation as resolved.
[DataRow("osx-arm64")]
public async Task Publish_WithNonWindowsRuntimeIdentifier_DoesNotCopyWindowsDlls(string runtimeIdentifier)
{
string publishPath = await this.CreateAndPublishTestProjectAsync($"NonWinTest_{runtimeIdentifier}", runtimeIdentifier);

this.AssertWindowsDllsNotPresent(publishPath, runtimeIdentifier);
}

/// <summary>
/// Tests that Windows native DLLs are copied when publishing for Windows platforms.
/// </summary>
/// <param name="runtimeIdentifier">The runtime identifier to test (e.g., win-x64, win-x86).</param>
[TestMethod]
[DataRow("win-x64")]
[DataRow("win-x86")]
[DataRow("win-arm64")]
public async Task Publish_WithWindowsRuntimeIdentifier_CopiesWindowsDlls(string runtimeIdentifier)
{
string publishPath = await this.CreateAndPublishTestProjectAsync($"WinTest_{runtimeIdentifier}", runtimeIdentifier);

this.AssertWindowsDllsPresent(publishPath, runtimeIdentifier);
}

/// <summary>
/// Tests that Windows native DLLs are copied when publishing without a RuntimeIdentifier,
/// which is the most common developer scenario (regular 'dotnet publish' without -r).
/// </summary>
[TestMethod]
public async Task Publish_WithoutRuntimeIdentifier_CopiesWindowsDlls()
{
string publishPath = await this.CreateAndPublishTestProjectAsync("NoRidTest", runtimeIdentifier: null);

this.AssertWindowsDllsPresent(publishPath, "no RuntimeIdentifier");
}

private static async Task CreateLocalNuGetPackageAsync()
{
string repoRoot = GetRepositoryRoot();
string cosmosProjectPath = Path.Combine(repoRoot, "Microsoft.Azure.Cosmos", "src", "Microsoft.Azure.Cosmos.csproj");
string packOutputDir = Path.Combine(testProjectsRoot, "nuget-packages");
Directory.CreateDirectory(packOutputDir);

// Use a unique version to avoid cache conflicts
packageVersion = $"99.0.0-test.{DateTime.UtcNow:yyyyMMddHHmmss}";

await RunDotnetCommandAsync(
$"pack \"{cosmosProjectPath}\" -c Release -o \"{packOutputDir}\" /p:Version={packageVersion} /p:PackageVersion={packageVersion}",
timeoutMinutes: 10);

localNugetPackagePath = packOutputDir;
}

private async Task<string> CreateAndPublishTestProjectAsync(string projectName, string runtimeIdentifier)
{
string projectDir = Path.Combine(testProjectsRoot, projectName);
Directory.CreateDirectory(projectDir);

string projectFile = Path.Combine(projectDir, $"{projectName}.csproj");
string programFile = Path.Combine(projectDir, "Program.cs");
string nugetConfigFile = Path.Combine(projectDir, "nuget.config");

// Create nuget.config to use local package source
File.WriteAllText(nugetConfigFile, $@"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<clear />
<add key=""local"" value=""{localNugetPackagePath}"" />
<add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" />
</packageSources>
</configuration>");
Comment thread
kirankumarkolli marked this conversation as resolved.

// Create a simple console app project that references the local NuGet package
File.WriteAllText(projectFile, $@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include=""Microsoft.Azure.Cosmos"" Version=""{packageVersion}"" />
<PackageReference Include=""Newtonsoft.Json"" Version=""13.0.3"" />
</ItemGroup>
</Project>");

// Create a minimal Program.cs
File.WriteAllText(programFile, @"System.Console.WriteLine(""Test app for verifying Cosmos SDK package behavior"");");

// Publish the project
string publishDir = Path.Combine(projectDir, "bin", "publish", runtimeIdentifier ?? "no-rid");
string ridArgument = runtimeIdentifier != null ? $"-r {runtimeIdentifier} --self-contained false" : string.Empty;
await RunDotnetCommandAsync(
$"publish \"{projectFile}\" -c Release -o \"{publishDir}\" {ridArgument}".Trim(),
timeoutMinutes: 5);

return publishDir;
}

private void AssertWindowsDllsNotPresent(string publishPath, string runtimeIdentifier)
{
Assert.IsTrue(Directory.Exists(publishPath), $"Publish directory does not exist: {publishPath}");
this.AssertPublishOutputIsValid(publishPath, runtimeIdentifier);

foreach (string dll in WindowsNativeDlls)
{
string dllPath = Path.Combine(publishPath, dll);
Assert.IsFalse(File.Exists(dllPath),
$"Windows native DLL '{dll}' should NOT be present when publishing for {runtimeIdentifier}, but was found at: {dllPath}");
}
}

private void AssertWindowsDllsPresent(string publishPath, string runtimeIdentifier)
{
Assert.IsTrue(Directory.Exists(publishPath), $"Publish directory does not exist: {publishPath}");
this.AssertPublishOutputIsValid(publishPath, runtimeIdentifier);

foreach (string dll in WindowsNativeDlls)
{
string dllPath = Path.Combine(publishPath, dll);
Assert.IsTrue(File.Exists(dllPath),
$"Windows native DLL '{dll}' SHOULD be present when publishing for {runtimeIdentifier}, but was NOT found at: {dllPath}");
}
}

private void AssertPublishOutputIsValid(string publishPath, string runtimeIdentifier)
{
string[] publishedFiles = Directory.GetFiles(publishPath);
Assert.IsTrue(publishedFiles.Length > 0,
$"Publish directory is empty for {runtimeIdentifier}. Publish may have silently failed: {publishPath}");

string sdkDllPath = Path.Combine(publishPath, "Microsoft.Azure.Cosmos.Client.dll");
Assert.IsTrue(File.Exists(sdkDllPath),
$"Microsoft.Azure.Cosmos.Client.dll not found in publish output for {runtimeIdentifier}. " +
$"Publish may not have included the SDK package correctly: {publishPath}");
}

private static string GetRepositoryRoot()
{
string currentDir = AppDomain.CurrentDomain.BaseDirectory;

while (currentDir != null && !File.Exists(Path.Combine(currentDir, "Microsoft.Azure.Cosmos.sln")))
{
DirectoryInfo parent = Directory.GetParent(currentDir);
if (parent == null)
{
break;
}
currentDir = parent.FullName;
}

Assert.IsNotNull(currentDir, "Could not find repository root");
return currentDir;
}

private static async Task RunDotnetCommandAsync(string arguments, int timeoutMinutes = 5)
{
ProcessStartInfo processInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

string commandLine = $"dotnet {arguments}";
Console.WriteLine($"Executing: {commandLine}");

Process process = Process.Start(processInfo);
if (process == null)
{
Assert.Fail($"Failed to start process: {commandLine}");
}

using (process)
{
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();
Task<string> errorTask = process.StandardError.ReadToEndAsync();

bool exited = process.WaitForExit((int)TimeSpan.FromMinutes(timeoutMinutes).TotalMilliseconds);
if (!exited)
{
process.Kill();
Assert.Fail($"Command timed out after {timeoutMinutes} minutes.\nCommand: {commandLine}");
}

string output = await outputTask;
string error = await errorTask;

if (process.ExitCode != 0)
{
Assert.Fail($"Command failed with exit code {process.ExitCode}.\nCommand: {commandLine}\nOutput: {output}\nError: {error}");
}

Assert.IsTrue(string.IsNullOrEmpty(error), $"Command had unexpected error output.\nCommand: {commandLine}\nError: {error}");
Console.WriteLine($"Command succeeded: {commandLine}");
}
}
}
}
Loading