From 0a4be4a989788fa7f920b1ff8bff3326970aea53 Mon Sep 17 00:00:00 2001
From: Sebastian Poxhofer <secustor@users.noreply.github.com>
Date: Tue, 30 May 2023 07:25:17 +0200
Subject: [PATCH] fix(manager/pep621): group pdm binary calls based on
 dependency groups (#22489)

---
 lib/modules/manager/pep621/extract.ts         |  5 +-
 .../manager/pep621/processors/pdm.spec.ts     | 33 ++++++++++-
 lib/modules/manager/pep621/processors/pdm.ts  | 58 ++++++++++++++++---
 lib/modules/manager/pep621/utils.ts           |  6 ++
 4 files changed, 91 insertions(+), 11 deletions(-)

diff --git a/lib/modules/manager/pep621/extract.ts b/lib/modules/manager/pep621/extract.ts
index f0710934a0..b8ba23a76b 100644
--- a/lib/modules/manager/pep621/extract.ts
+++ b/lib/modules/manager/pep621/extract.ts
@@ -7,6 +7,7 @@ import type {
 } from '../types';
 import { processors } from './processors';
 import {
+  depTypes,
   parseDependencyGroupRecord,
   parseDependencyList,
   parsePyProject,
@@ -32,11 +33,11 @@ export function extractPackageFile(
 
   // pyProject standard definitions
   deps.push(
-    ...parseDependencyList('project.dependencies', def.project?.dependencies)
+    ...parseDependencyList(depTypes.dependencies, def.project?.dependencies)
   );
   deps.push(
     ...parseDependencyGroupRecord(
-      'project.optional-dependencies',
+      depTypes.optionalDependencies,
       def.project?.['optional-dependencies']
     )
   );
diff --git a/lib/modules/manager/pep621/processors/pdm.spec.ts b/lib/modules/manager/pep621/processors/pdm.spec.ts
index a09f5082e4..a7588535d0 100644
--- a/lib/modules/manager/pep621/processors/pdm.spec.ts
+++ b/lib/modules/manager/pep621/processors/pdm.spec.ts
@@ -5,6 +5,7 @@ import { GlobalConfig } from '../../../../config/global';
 import type { RepoGlobalConfig } from '../../../../config/types';
 import { getPkgReleases as _getPkgReleases } from '../../../datasource';
 import type { UpdateArtifactsConfig } from '../../types';
+import { depTypes } from '../utils';
 import { PdmProcessor } from './pdm';
 
 jest.mock('../../../../util/fs');
@@ -130,7 +131,28 @@ describe('modules/manager/pep621/processors/pdm', () => {
         releases: [{ version: 'v2.6.1' }, { version: 'v2.5.0' }],
       });
 
-      const updatedDeps = [{ packageName: 'dep1' }, { packageName: 'dep2' }];
+      const updatedDeps = [
+        {
+          packageName: 'dep1',
+          depType: depTypes.dependencies,
+        },
+        { packageName: 'dep2', depType: depTypes.dependencies },
+        {
+          depName: 'group1/dep3',
+          depType: depTypes.optionalDependencies,
+        },
+        { depName: 'group1/dep4', depType: depTypes.optionalDependencies },
+        {
+          depName: 'group2/dep5',
+          depType: depTypes.pdmDevDependencies,
+        },
+        { depName: 'group2/dep6', depType: depTypes.pdmDevDependencies },
+        {
+          depName: 'group3/dep7',
+          depType: depTypes.pdmDevDependencies,
+        },
+        { depName: 'group3/dep8', depType: depTypes.pdmDevDependencies },
+      ];
       const result = await processor.updateArtifacts(
         {
           packageFileName: 'pyproject.toml',
@@ -153,6 +175,15 @@ describe('modules/manager/pep621/processors/pdm', () => {
         {
           cmd: 'pdm update dep1 dep2',
         },
+        {
+          cmd: 'pdm update -G group1 dep3 dep4',
+        },
+        {
+          cmd: 'pdm update -dG group2 dep5 dep6',
+        },
+        {
+          cmd: 'pdm update -dG group3 dep7 dep8',
+        },
       ]);
     });
 
diff --git a/lib/modules/manager/pep621/processors/pdm.ts b/lib/modules/manager/pep621/processors/pdm.ts
index 3730cd8b33..365c2d2a64 100644
--- a/lib/modules/manager/pep621/processors/pdm.ts
+++ b/lib/modules/manager/pep621/processors/pdm.ts
@@ -9,9 +9,10 @@ import type {
   PackageDependency,
   UpdateArtifact,
   UpdateArtifactsResult,
+  Upgrade,
 } from '../../types';
 import type { PyProject } from '../schema';
-import { parseDependencyGroupRecord } from '../utils';
+import { depTypes, parseDependencyGroupRecord } from '../utils';
 import type { PyProjectProcessor } from './types';
 
 export class PdmProcessor implements PyProjectProcessor {
@@ -23,7 +24,7 @@ export class PdmProcessor implements PyProjectProcessor {
 
     deps.push(
       ...parseDependencyGroupRecord(
-        'tool.pdm.dev-dependencies',
+        depTypes.pdmDevDependencies,
         pdm['dev-dependencies']
       )
     );
@@ -84,13 +85,13 @@ export class PdmProcessor implements PyProjectProcessor {
 
       // on lockFileMaintenance do not specify any packages and update the complete lock file
       // else only update specific packages
-      let packageList = '';
-      if (!isLockFileMaintenance) {
-        packageList = ' ';
-        packageList += updatedDeps.map((value) => value.packageName).join(' ');
+      const cmds: string[] = [];
+      if (isLockFileMaintenance) {
+        cmds.push('pdm update');
+      } else {
+        cmds.push(...generateCMDs(updatedDeps));
       }
-      const cmd = `pdm update${packageList}`;
-      await exec(cmd, execOptions);
+      await exec(cmds, execOptions);
 
       // check for changes
       const fileChanges: UpdateArtifactsResult[] = [];
@@ -126,3 +127,44 @@ export class PdmProcessor implements PyProjectProcessor {
     }
   }
 }
+
+function generateCMDs(updatedDeps: Upgrade[]): string[] {
+  const cmds: string[] = [];
+  const packagesByCMD: Record<string, string[]> = {};
+  for (const dep of updatedDeps) {
+    switch (dep.depType) {
+      case depTypes.optionalDependencies: {
+        const [group, name] = dep.depName!.split('/');
+        addPackageToCMDRecord(packagesByCMD, `pdm update -G ${group}`, name);
+        break;
+      }
+      case depTypes.pdmDevDependencies: {
+        const [group, name] = dep.depName!.split('/');
+        addPackageToCMDRecord(packagesByCMD, `pdm update -dG ${group}`, name);
+        break;
+      }
+      default: {
+        addPackageToCMDRecord(packagesByCMD, `pdm update`, dep.packageName!);
+      }
+    }
+  }
+
+  for (const commandPrefix in packagesByCMD) {
+    const packageList = packagesByCMD[commandPrefix].join(' ');
+    const cmd = `${commandPrefix} ${packageList}`;
+    cmds.push(cmd);
+  }
+
+  return cmds;
+}
+
+function addPackageToCMDRecord(
+  packagesByCMD: Record<string, string[]>,
+  commandPrefix: string,
+  packageName: string
+): void {
+  if (is.nullOrUndefined(packagesByCMD[commandPrefix])) {
+    packagesByCMD[commandPrefix] = [];
+  }
+  packagesByCMD[commandPrefix].push(packageName);
+}
diff --git a/lib/modules/manager/pep621/utils.ts b/lib/modules/manager/pep621/utils.ts
index bc1ac4cce2..34dc7c6da7 100644
--- a/lib/modules/manager/pep621/utils.ts
+++ b/lib/modules/manager/pep621/utils.ts
@@ -11,6 +11,12 @@ const pep508Regex = regEx(
   /^(?<packageName>[A-Z0-9._-]+)\s*(\[(?<extras>[A-Z0-9,._-]+)\])?\s*(?<currentValue>[^;]+)?(;\s*(?<marker>.*))?/i
 );
 
+export const depTypes = {
+  dependencies: 'project.dependencies',
+  optionalDependencies: 'project.optional-dependencies',
+  pdmDevDependencies: 'tool.pdm.dev-dependencies',
+};
+
 export function parsePEP508(
   value: string | null | undefined
 ): Pep508ParseResult | null {
-- 
GitLab