From c1da6b948d5d93dbd883397ae467255f6d840c32 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Tue, 24 May 2022 16:29:28 +0200 Subject: [PATCH] feat(manager:nuget): support central version management (#15698) --- .../Directory.Packages.props | 8 ++++ .../one/one.csproj | 12 ++++++ .../one/packages.lock.json | 13 ++++++ .../two/packages.lock.json | 19 +++++++++ .../two/two.csproj | 16 ++++++++ lib/modules/manager/nuget/artifacts.ts | 26 ++++++++++-- .../manager/nuget/package-tree.spec.ts | 19 +++++++++ lib/modules/manager/nuget/package-tree.ts | 40 ++++++++++++++----- 8 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/Directory.Packages.props create mode 100644 lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/one.csproj create mode 100644 lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/packages.lock.json create mode 100644 lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/packages.lock.json create mode 100644 lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/two.csproj diff --git a/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/Directory.Packages.props b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/Directory.Packages.props new file mode 100644 index 0000000000..3478eb88ed --- /dev/null +++ b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/Directory.Packages.props @@ -0,0 +1,8 @@ + + + true + + + + + diff --git a/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/one.csproj b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/one.csproj new file mode 100644 index 0000000000..ad57bdc089 --- /dev/null +++ b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/one.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + true + + + + + + + diff --git a/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/packages.lock.json b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/packages.lock.json new file mode 100644 index 0000000000..374c404b2d --- /dev/null +++ b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/one/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "dependencies": { + ".NETCoreApp,Version=v5.0": { + "Serilog": { + "type": "Direct", + "requested": "[2.10.0, )", + "resolved": "2.10.0", + "contentHash": "+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==" + } + } + } +} \ No newline at end of file diff --git a/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/packages.lock.json b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/packages.lock.json new file mode 100644 index 0000000000..426b0a04bf --- /dev/null +++ b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/packages.lock.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "dependencies": { + ".NETCoreApp,Version=v5.0": { + "Serilog": { + "type": "Direct", + "requested": "[2.10.0, )", + "resolved": "2.10.0", + "contentHash": "+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==" + }, + "one": { + "type": "Project", + "dependencies": { + "Serilog": "2.10.0" + } + } + } + } +} \ No newline at end of file diff --git a/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/two.csproj b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/two.csproj new file mode 100644 index 0000000000..49ce5bf29d --- /dev/null +++ b/lib/modules/manager/nuget/__fixtures__/two-one-reference-with-central-versions/two/two.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + true + + + + + + + + + + + diff --git a/lib/modules/manager/nuget/artifacts.ts b/lib/modules/manager/nuget/artifacts.ts index 985062c6fa..7869790a97 100644 --- a/lib/modules/manager/nuget/artifacts.ts +++ b/lib/modules/manager/nuget/artifacts.ts @@ -22,7 +22,11 @@ import type { UpdateArtifactsConfig, UpdateArtifactsResult, } from '../types'; -import { getDependentPackageFiles } from './package-tree'; +import { + MSBUILD_CENTRAL_FILE, + NUGET_CENTRAL_FILE, + getDependentPackageFiles, +} from './package-tree'; import { getConfiguredRegistries, getDefaultRegistries, @@ -116,7 +120,18 @@ export async function updateArtifacts({ }: UpdateArtifact): Promise { logger.debug(`nuget.updateArtifacts(${packageFileName})`); - if (!regEx(/(?:cs|vb|fs)proj$/i).test(packageFileName)) { + // https://github.com/NuGet/Home/wiki/Centrally-managing-NuGet-package-versions + // https://github.com/microsoft/MSBuildSdks/tree/main/src/CentralPackageVersions + const isCentralManament = + packageFileName === NUGET_CENTRAL_FILE || + packageFileName === MSBUILD_CENTRAL_FILE || + packageFileName.endsWith(`/${NUGET_CENTRAL_FILE}`) || + packageFileName.endsWith(`/${MSBUILD_CENTRAL_FILE}`); + + if ( + !isCentralManament && + !regEx(/(?:cs|vb|fs)proj$/i).test(packageFileName) + ) { // This could be implemented in the future if necessary. // It's not that easy though because the questions which // project file to restore how to determine which lock files @@ -129,10 +144,13 @@ export async function updateArtifacts({ } const packageFiles = [ - ...(await getDependentPackageFiles(packageFileName)), - packageFileName, + ...(await getDependentPackageFiles(packageFileName, isCentralManament)), ]; + if (!isCentralManament) { + packageFiles.push(packageFileName); + } + logger.trace( { packageFiles }, `Found ${packageFiles.length} dependent package files` diff --git a/lib/modules/manager/nuget/package-tree.spec.ts b/lib/modules/manager/nuget/package-tree.spec.ts index 499b841a5c..5725b7997c 100644 --- a/lib/modules/manager/nuget/package-tree.spec.ts +++ b/lib/modules/manager/nuget/package-tree.spec.ts @@ -63,6 +63,25 @@ describe('modules/manager/nuget/package-tree', () => { ]); }); + it('returns projects for two projects with one reference and central versions', async () => { + git.getFileList.mockResolvedValue(['one/one.csproj', 'two/two.csproj']); + Fixtures.mock({ + '/tmp/repo/one/one.csproj': Fixtures.get( + 'two-one-reference-with-central-versions/one/one.csproj' + ), + '/tmp/repo/two/two.csproj': Fixtures.get( + 'two-one-reference-with-central-versions/two/two.csproj' + ), + '/tmp/repo/Directory.Packages.props': Fixtures.get( + 'two-one-reference-with-central-versions/Directory.Packages.props' + ), + }); + + expect( + await getDependentPackageFiles('Directory.Packages.props', true) + ).toEqual(['one/one.csproj', 'two/two.csproj']); + }); + it('returns two projects for three projects with two linear references', async () => { git.getFileList.mockResolvedValue([ 'one/one.csproj', diff --git a/lib/modules/manager/nuget/package-tree.ts b/lib/modules/manager/nuget/package-tree.ts index 6d697d11e4..d2510f0ebc 100644 --- a/lib/modules/manager/nuget/package-tree.ts +++ b/lib/modules/manager/nuget/package-tree.ts @@ -6,28 +6,44 @@ import { logger } from '../../../logger'; import { readLocalFile } from '../../../util/fs'; import { getFileList } from '../../../util/git'; +export const NUGET_CENTRAL_FILE = 'Directory.Packages.props'; +export const MSBUILD_CENTRAL_FILE = 'Packages.props'; + /** * Get all package files at any level of ancestry that depend on packageFileName */ export async function getDependentPackageFiles( - packageFileName: string + packageFileName: string, + isCentralManament = false ): Promise { const packageFiles = await getAllPackageFiles(); const graph: ReturnType = Graph(); + if (isCentralManament) { + graph.addNode(packageFileName); + } + + const parentDir = + packageFileName === NUGET_CENTRAL_FILE || + packageFileName === MSBUILD_CENTRAL_FILE + ? '' + : upath.dirname(packageFileName); + for (const f of packageFiles) { graph.addNode(f); + + if (isCentralManament && upath.dirname(f).startsWith(parentDir)) { + graph.addEdge(packageFileName, f); + } } for (const f of packageFiles) { - const packageFileContent = (await readLocalFile(f, 'utf8')).toString(); + const packageFileContent = await readLocalFile(f, 'utf8'); const doc = new xmldoc.XmlDocument(packageFileContent); - const projectReferenceAttributes = ( - doc - .childrenNamed('ItemGroup') - .map((ig) => ig.childrenNamed('ProjectReference')) ?? [] - ) + const projectReferenceAttributes = doc + .childrenNamed('ItemGroup') + .map((ig) => ig.childrenNamed('ProjectReference')) .flat() .map((pf) => pf.attr['Include']); @@ -47,7 +63,13 @@ export async function getDependentPackageFiles( } } - return recursivelyGetDependentPackageFiles(packageFileName, graph); + const dependents = recursivelyGetDependentPackageFiles( + packageFileName, + graph + ); + + // deduplicate + return Array.from(new Set(dependents.reverse())).reverse(); } /** @@ -57,7 +79,7 @@ function recursivelyGetDependentPackageFiles( packageFileName: string, graph: ReturnType ): string[] { - const dependents: string[] = graph.adjacent(packageFileName); + const dependents = graph.adjacent(packageFileName); if (dependents.length === 0) { return [];