Skip to content

Commit bd80e58

Browse files
brettfoCopilot
authored andcommitted
NuGet: Add FindRootDirectory experiment to resolve root entry points
When the `nuget-find-root-directory` experiment flag is enabled, the RunWorker scans the repo for .sln, .slnx, and .proj files and builds a child-to-parent mapping. It then walks upward from each job directory's project files to find the ultimate root entry points, replacing the job's directory list with the minimal ancestor set. This enables Dependabot to discover and update all projects reachable from a common root, even when the job is initially scoped to a single subdirectory. Includes: - EntryPointFinder utility with scanning, parent map building, and root walking - MSBuild evaluation for .proj files supporting globs, properties like $(MSBuildThisFileDirectory), and explicit references - Integration in RunWorker behind the experiment flag - 17 unit tests covering explicit refs, globs, MSBuild properties, transitive chains, cycles, and edge cases - 1 end-to-end test proving root directory expansion across a .proj chain with two projects in different directories Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d314e4b commit bd80e58

7 files changed

Lines changed: 953 additions & 1 deletion

File tree

nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/EntryPointFinderTests.cs

Lines changed: 430 additions & 0 deletions
Large diffs are not rendered by default.

nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,234 @@ await UpdateWorkerTestBase.MockNuGetPackagesInDirectory([
13811381
Assert.Equal("2.1.0", resolvedInLibrary);
13821382
}
13831383

1384+
[Fact]
1385+
public async Task FindRootDirectory_ExpandsDirectoryThroughProjChain()
1386+
{
1387+
// Job starts at /src/client, but a .proj chain leads up to the repo root.
1388+
// With FindRootDirectory enabled, the job should discover from "/" instead,
1389+
// which also picks up /test/client/client-test.csproj that would not be found
1390+
// if only /src/client were examined.
1391+
await RunAsync(
1392+
packages: [
1393+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net9.0"),
1394+
MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net9.0"),
1395+
],
1396+
job: new()
1397+
{
1398+
Source = new()
1399+
{
1400+
Provider = "github",
1401+
Repo = "test/repo",
1402+
Directory = "/src/client",
1403+
}
1404+
},
1405+
files: [
1406+
("Directory.Build.props", "<Project />"),
1407+
("Directory.Build.targets", "<Project />"),
1408+
("Directory.Packages.props", """
1409+
<Project>
1410+
<PropertyGroup>
1411+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
1412+
</PropertyGroup>
1413+
</Project>
1414+
"""),
1415+
("dirs.proj", """
1416+
<Project>
1417+
<ItemGroup>
1418+
<ProjectFile Include="src\dirs.proj" />
1419+
<ProjectFile Include="test\dirs.proj" />
1420+
</ItemGroup>
1421+
</Project>
1422+
"""),
1423+
("src/dirs.proj", """
1424+
<Project>
1425+
<ItemGroup>
1426+
<ProjectFile Include="client\client.csproj" />
1427+
</ItemGroup>
1428+
</Project>
1429+
"""),
1430+
("test/dirs.proj", """
1431+
<Project>
1432+
<ItemGroup>
1433+
<ProjectFile Include="client\client-test.csproj" />
1434+
</ItemGroup>
1435+
</Project>
1436+
"""),
1437+
("src/client/client.csproj", """
1438+
<Project Sdk="Microsoft.NET.Sdk">
1439+
<PropertyGroup>
1440+
<TargetFramework>net9.0</TargetFramework>
1441+
</PropertyGroup>
1442+
<ItemGroup>
1443+
<PackageReference Include="Some.Package" Version="1.0.0" />
1444+
</ItemGroup>
1445+
</Project>
1446+
"""),
1447+
("test/client/client-test.csproj", """
1448+
<Project Sdk="Microsoft.NET.Sdk">
1449+
<PropertyGroup>
1450+
<TargetFramework>net9.0</TargetFramework>
1451+
</PropertyGroup>
1452+
<ItemGroup>
1453+
<PackageReference Include="Some.Package" Version="1.0.0" />
1454+
<ProjectReference Include="..\..\src\client\client.csproj" />
1455+
</ItemGroup>
1456+
</Project>
1457+
"""),
1458+
],
1459+
discoveryWorker: null,
1460+
analyzeWorker: null,
1461+
updaterWorker: null,
1462+
experimentsManager: new ExperimentsManager() { FindRootDirectory = true },
1463+
expectedApiMessages: [
1464+
new IncrementMetric()
1465+
{
1466+
Metric = "updater.started",
1467+
Tags = new()
1468+
{
1469+
["operation"] = "group_update_all_versions"
1470+
}
1471+
},
1472+
new UpdatedDependencyList()
1473+
{
1474+
Dependencies = [
1475+
new()
1476+
{
1477+
Name = "Some.Package",
1478+
Version = "1.0.0",
1479+
Requirements = [
1480+
new()
1481+
{
1482+
Requirement = "1.0.0",
1483+
File = "/src/client/client.csproj",
1484+
Groups = ["dependencies"],
1485+
}
1486+
]
1487+
},
1488+
new()
1489+
{
1490+
Name = "Some.Package",
1491+
Version = "1.0.0",
1492+
Requirements = [
1493+
new()
1494+
{
1495+
Requirement = "1.0.0",
1496+
File = "/test/client/client-test.csproj",
1497+
Groups = ["dependencies"],
1498+
}
1499+
]
1500+
},
1501+
],
1502+
DependencyFiles = [
1503+
"/Directory.Build.props",
1504+
"/Directory.Build.targets",
1505+
"/Directory.Packages.props",
1506+
"/src/client/client.csproj",
1507+
"/test/client/client-test.csproj",
1508+
],
1509+
},
1510+
new CreatePullRequest()
1511+
{
1512+
Dependencies = [
1513+
new()
1514+
{
1515+
Name = "Some.Package",
1516+
Version = "2.0.0",
1517+
Requirements = [
1518+
new()
1519+
{
1520+
Requirement = "2.0.0",
1521+
File = "/src/client/client.csproj",
1522+
Groups = ["dependencies"],
1523+
Source = new()
1524+
{
1525+
SourceUrl = null,
1526+
Type = "nuget_repo",
1527+
}
1528+
}
1529+
],
1530+
PreviousVersion = "1.0.0",
1531+
PreviousRequirements = [
1532+
new()
1533+
{
1534+
Requirement = "1.0.0",
1535+
File = "/src/client/client.csproj",
1536+
Groups = ["dependencies"],
1537+
}
1538+
],
1539+
},
1540+
new()
1541+
{
1542+
Name = "Some.Package",
1543+
Version = "2.0.0",
1544+
Requirements = [
1545+
new()
1546+
{
1547+
Requirement = "2.0.0",
1548+
File = "/test/client/client-test.csproj",
1549+
Groups = ["dependencies"],
1550+
Source = new()
1551+
{
1552+
SourceUrl = null,
1553+
Type = "nuget_repo",
1554+
}
1555+
}
1556+
],
1557+
PreviousVersion = "1.0.0",
1558+
PreviousRequirements = [
1559+
new()
1560+
{
1561+
Requirement = "1.0.0",
1562+
File = "/test/client/client-test.csproj",
1563+
Groups = ["dependencies"],
1564+
}
1565+
],
1566+
},
1567+
],
1568+
UpdatedDependencyFiles = [
1569+
new()
1570+
{
1571+
Directory = "/src/client",
1572+
Name = "client.csproj",
1573+
Content = """
1574+
<Project Sdk="Microsoft.NET.Sdk">
1575+
<PropertyGroup>
1576+
<TargetFramework>net9.0</TargetFramework>
1577+
</PropertyGroup>
1578+
<ItemGroup>
1579+
<PackageReference Include="Some.Package" Version="2.0.0" />
1580+
</ItemGroup>
1581+
</Project>
1582+
"""
1583+
},
1584+
new()
1585+
{
1586+
Directory = "/test/client",
1587+
Name = "client-test.csproj",
1588+
Content = """
1589+
<Project Sdk="Microsoft.NET.Sdk">
1590+
<PropertyGroup>
1591+
<TargetFramework>net9.0</TargetFramework>
1592+
</PropertyGroup>
1593+
<ItemGroup>
1594+
<PackageReference Include="Some.Package" Version="2.0.0" />
1595+
<ProjectReference Include="..\..\src\client\client.csproj" />
1596+
</ItemGroup>
1597+
</Project>
1598+
"""
1599+
},
1600+
],
1601+
BaseCommitSha = "TEST-COMMIT-SHA",
1602+
CommitMessage = TestPullRequestCommitMessage,
1603+
PrTitle = TestPullRequestTitle,
1604+
PrBody = TestPullRequestBody,
1605+
DependencyGroup = null,
1606+
},
1607+
new MarkAsProcessed("TEST-COMMIT-SHA")
1608+
]
1609+
);
1610+
}
1611+
13841612
public const string TestPullRequestCommitMessage = "test-pull-request-commit-message";
13851613
public const string TestPullRequestTitle = "test-pull-request-title";
13861614
public const string TestPullRequestBody = "test-pull-request-body";

0 commit comments

Comments
 (0)