diff --git a/lib/manager/nuget/__fixtures__/msbuild-sdk-files/global.json b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/global.json new file mode 100644 index 00000000000000..8319e185529cdc --- /dev/null +++ b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "5.0.302", + "rollForward": "latestMajor" + }, + "msbuild-sdks": { + "YoloDev.Sdk": "0.2.0" + } +} diff --git a/lib/manager/nuget/__fixtures__/msbuild-sdk-files/invalid-json/global.json b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/invalid-json/global.json new file mode 100644 index 00000000000000..39a1b3463f5632 --- /dev/null +++ b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/invalid-json/global.json @@ -0,0 +1 @@ +invalid json diff --git a/lib/manager/nuget/__fixtures__/msbuild-sdk-files/not-nuget/global.json b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/not-nuget/global.json new file mode 100644 index 00000000000000..72989a50028dfd --- /dev/null +++ b/lib/manager/nuget/__fixtures__/msbuild-sdk-files/not-nuget/global.json @@ -0,0 +1,3 @@ +{ + "type": "not a nuget global.json" +} diff --git a/lib/manager/nuget/extract.spec.ts b/lib/manager/nuget/extract.spec.ts index af9392d5050541..59099efb8d6c5f 100644 --- a/lib/manager/nuget/extract.spec.ts +++ b/lib/manager/nuget/extract.spec.ts @@ -114,6 +114,46 @@ describe('manager/nuget/extract', () => { ).toMatchSnapshot(); }); + it('extracts msbuild-sdks from global.json', async () => { + const packageFile = 'msbuild-sdk-files/global.json'; + const contents = loadFixture(packageFile); + expect(await extractPackageFile(contents, packageFile, config)) + .toMatchInlineSnapshot(` + Object { + "deps": Array [ + Object { + "currentValue": "5.0.302", + "depName": "dotnet-sdk", + "depType": "dotnet-sdk", + "skipReason": "unsupported-datasource", + }, + Object { + "currentValue": "0.2.0", + "datasource": "nuget", + "depName": "YoloDev.Sdk", + "depType": "msbuild-sdk", + }, + ], + } + `); + }); + + it('handles malformed global.json', async () => { + const packageFile = 'msbuild-sdk-files/invalid-json/global.json'; + const contents = loadFixture(packageFile); + expect( + await extractPackageFile(contents, packageFile, config) + ).toBeNull(); + }); + + it('handles not-a-nuget global.json', async () => { + const packageFile = 'msbuild-sdk-files/not-nuget/global.json'; + const contents = loadFixture(packageFile); + expect( + await extractPackageFile(contents, packageFile, config) + ).toBeNull(); + }); + describe('.config/dotnet-tools.json', () => { const packageFile = '.config/dotnet-tools.json'; const contents = `{ @@ -126,6 +166,7 @@ describe('manager/nuget/extract', () => { } } }`; + it('works', async () => { // FIXME: explicit assert condition expect( diff --git a/lib/manager/nuget/extract.ts b/lib/manager/nuget/extract.ts index 8f23b34d36975e..f4d3af4cb25809 100644 --- a/lib/manager/nuget/extract.ts +++ b/lib/manager/nuget/extract.ts @@ -5,6 +5,7 @@ import { logger } from '../../logger'; import { getSiblingFileName, localPathExists } from '../../util/fs'; import { hasKey } from '../../util/object'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; +import { extractMsbuildGlobalManifest } from './extract/global-manifest'; import type { DotnetToolsManifest } from './types'; import { getConfiguredRegistries } from './util'; @@ -112,6 +113,10 @@ export async function extractPackageFile( return { deps }; } + if (packageFile.endsWith('global.json')) { + return extractMsbuildGlobalManifest(content, packageFile); + } + let deps: PackageDependency[] = []; try { const parsedXml = new XmlDocument(content); diff --git a/lib/manager/nuget/extract/global-manifest.ts b/lib/manager/nuget/extract/global-manifest.ts new file mode 100644 index 00000000000000..48fe0a1939adba --- /dev/null +++ b/lib/manager/nuget/extract/global-manifest.ts @@ -0,0 +1,51 @@ +import * as datasourceNuget from '../../../datasource/nuget'; +import { logger } from '../../../logger'; +import { SkipReason } from '../../../types'; +import type { PackageDependency, PackageFile } from '../../types'; +import type { MsbuildGlobalManifest } from '../types'; + +export function extractMsbuildGlobalManifest( + content: string, + packageFile: string +): PackageFile | null { + const deps: PackageDependency[] = []; + let manifest: MsbuildGlobalManifest; + + try { + manifest = JSON.parse(content); + } catch (err) { + logger.debug({ fileName: packageFile }, 'Invalid JSON'); + return null; + } + + if (!manifest['msbuild-sdks'] && !manifest.sdk?.version) { + logger.debug( + { fileName: packageFile }, + 'This global.json is not a Nuget file' + ); + return null; + } + + if (manifest.sdk?.version) { + deps.push({ + depType: 'dotnet-sdk', + depName: 'dotnet-sdk', + currentValue: manifest.sdk?.version, + skipReason: SkipReason.UnsupportedDatasource, + }); + } + + for (const depName of Object.keys(manifest['msbuild-sdks'])) { + const currentValue = manifest['msbuild-sdks'][depName]; + const dep: PackageDependency = { + depType: 'msbuild-sdk', + depName, + currentValue, + datasource: datasourceNuget.id, + }; + + deps.push(dep); + } + + return { deps }; +} diff --git a/lib/manager/nuget/index.ts b/lib/manager/nuget/index.ts index 4311964f62c0d5..332a78be763e5a 100644 --- a/lib/manager/nuget/index.ts +++ b/lib/manager/nuget/index.ts @@ -10,5 +10,6 @@ export const defaultConfig = { '\\.(?:cs|fs|vb)proj$', '\\.(?:props|targets)$', '\\.config\\/dotnet-tools\\.json$', + '(^|//)global\\.json$', ], }; diff --git a/lib/manager/nuget/types.ts b/lib/manager/nuget/types.ts index 631f8114e7e78a..eae8865e24fa40 100644 --- a/lib/manager/nuget/types.ts +++ b/lib/manager/nuget/types.ts @@ -14,3 +14,13 @@ export interface Registry { readonly url: string; readonly name?: string; } + +export interface MsbuildGlobalManifest { + readonly sdk: MsbuildSdk; + readonly 'msbuild-sdks': Record; +} + +export interface MsbuildSdk { + readonly version: string; + readonly rollForward: string; +} diff --git a/lib/types/skip-reason.ts b/lib/types/skip-reason.ts index 501cce0a5c27bf..1f11d3b848c224 100644 --- a/lib/types/skip-reason.ts +++ b/lib/types/skip-reason.ts @@ -34,6 +34,7 @@ export enum SkipReason { UnknownVersion = 'unknown-version', UnknownVolta = 'unknown-volta', UnsupportedChartType = 'unsupported-chart-type', + UnsupportedDatasource = 'unsupported-datasource', UnsupportedRemote = 'unsupported-remote', UnsupportedUrl = 'unsupported-url', UnsupportedVersion = 'unsupported-version',