From 196df0d5d5fd778322b95a5feabf5105efd40f76 Mon Sep 17 00:00:00 2001
From: Oleg Krivtsov <olegkrivtsov@gmail.com>
Date: Wed, 15 Sep 2021 01:58:50 +0700
Subject: [PATCH] feat(manager/nuget): update msbuild-sdks section in
 global.json (#11707)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 .../msbuild-sdk-files/global.json             |  9 ++++
 .../invalid-json/global.json                  |  1 +
 .../msbuild-sdk-files/not-nuget/global.json   |  3 ++
 lib/manager/nuget/extract.spec.ts             | 41 +++++++++++++++
 lib/manager/nuget/extract.ts                  |  5 ++
 lib/manager/nuget/extract/global-manifest.ts  | 51 +++++++++++++++++++
 lib/manager/nuget/index.ts                    |  1 +
 lib/manager/nuget/types.ts                    | 10 ++++
 lib/types/skip-reason.ts                      |  1 +
 9 files changed, 122 insertions(+)
 create mode 100644 lib/manager/nuget/__fixtures__/msbuild-sdk-files/global.json
 create mode 100644 lib/manager/nuget/__fixtures__/msbuild-sdk-files/invalid-json/global.json
 create mode 100644 lib/manager/nuget/__fixtures__/msbuild-sdk-files/not-nuget/global.json
 create mode 100644 lib/manager/nuget/extract/global-manifest.ts

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 0000000000..8319e18552
--- /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 0000000000..39a1b3463f
--- /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 0000000000..72989a5002
--- /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 af9392d505..59099efb8d 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 8f23b34d36..f4d3af4cb2 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 0000000000..48fe0a1939
--- /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 4311964f62..332a78be76 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 631f8114e7..eae8865e24 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<string, string>;
+}
+
+export interface MsbuildSdk {
+  readonly version: string;
+  readonly rollForward: string;
+}
diff --git a/lib/types/skip-reason.ts b/lib/types/skip-reason.ts
index 501cce0a5c..1f11d3b848 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',
-- 
GitLab