|
| 1 | +import type { Pep440Version } from '@renovatebot/pep440'; |
| 2 | +import { CancellationToken, Disposable, Uri } from 'vscode'; |
| 3 | +import { |
| 4 | + Capabilities, |
| 5 | + CapabilityCheck, |
| 6 | + GetPackagesOptions, |
| 7 | + Package, |
| 8 | + PackageCapability, |
| 9 | + PackageManagementOptions, |
| 10 | + PackageManager, |
| 11 | + PackageManagerCapability, |
| 12 | + PythonEnvironment, |
| 13 | +} from '../../api'; |
| 14 | +import { PackageToolSupport, pythonToolSupport, throwIfCancelled } from '../../internal/pythonToolSupport'; |
| 15 | +import { shouldUseUv } from './helpers'; |
| 16 | +import { PipPackageManager } from './pipPackageManager'; |
| 17 | +import { UvPackageManager } from './uvPackageManager'; |
| 18 | + |
| 19 | +/** Routes the existing pip entry point and coordinates invalidation between independent backend caches. */ |
| 20 | +export class BuiltInPackageManager implements PackageManager, Disposable { |
| 21 | + private readonly cacheSubscriptions: Disposable[]; |
| 22 | + |
| 23 | + constructor( |
| 24 | + private readonly pip: PipPackageManager, |
| 25 | + private readonly uv: UvPackageManager, |
| 26 | + ) { |
| 27 | + this.pip.setPackageManagerOwner(this); |
| 28 | + this.cacheSubscriptions = [ |
| 29 | + this.pip.onDidMutatePackages(({ environment, packages }) => this.uv.invalidateCache(environment, packages)), |
| 30 | + this.uv.onDidMutatePackages(({ environment, packages }) => this.pip.invalidateCache(environment, packages)), |
| 31 | + ]; |
| 32 | + } |
| 33 | + |
| 34 | + readonly name = 'pip'; |
| 35 | + readonly displayName = 'Pip'; |
| 36 | + |
| 37 | + get description() { |
| 38 | + return this.pip.description; |
| 39 | + } |
| 40 | + get tooltip() { |
| 41 | + return this.pip.tooltip; |
| 42 | + } |
| 43 | + get iconPath() { |
| 44 | + return this.pip.iconPath; |
| 45 | + } |
| 46 | + get onDidChangePackages() { |
| 47 | + return this.pip.onDidChangePackages; |
| 48 | + } |
| 49 | + |
| 50 | + readonly capabilities: Capabilities<PackageManagerCapability> = { |
| 51 | + [PackageCapability.List]: this.routeCapability(PackageCapability.List), |
| 52 | + [PackageCapability.Install]: this.routeCapability(PackageCapability.Install), |
| 53 | + [PackageCapability.Uninstall]: this.routeCapability(PackageCapability.Uninstall), |
| 54 | + [PackageCapability.Upgrade]: this.routeCapability(PackageCapability.Upgrade), |
| 55 | + [PackageCapability.FetchAvailableVersions]: this.routeCapability(PackageCapability.FetchAvailableVersions), |
| 56 | + [PackageCapability.ListDirect]: this.routeCapability(PackageCapability.ListDirect), |
| 57 | + }; |
| 58 | + |
| 59 | + readonly [pythonToolSupport]: PackageToolSupport = { |
| 60 | + manage: (environment, options, token, scope) => |
| 61 | + this.manage(environment, { ...options, runHeadless: true }, token, scope), |
| 62 | + getPackages: async (environment, token, scope) => |
| 63 | + (await this.getManager(environment, token))[pythonToolSupport].getPackages(environment, token, scope), |
| 64 | + }; |
| 65 | + |
| 66 | + private async getManager( |
| 67 | + environment: PythonEnvironment, |
| 68 | + token?: CancellationToken, |
| 69 | + ): Promise<PipPackageManager | UvPackageManager> { |
| 70 | + throwIfCancelled(token); |
| 71 | + if (!environment.execInfo?.run?.executable) { |
| 72 | + return this.pip; |
| 73 | + } |
| 74 | + const envPath = environment.environmentPath.fsPath; |
| 75 | + return await shouldUseUv(this.pip.log, envPath, envPath, token) ? this.uv : this.pip; |
| 76 | + } |
| 77 | + |
| 78 | + private routeCapability(capability: PackageManagerCapability): CapabilityCheck { |
| 79 | + return async (context) => { |
| 80 | + const manager = context.environment ? await this.getManager(context.environment) : this.pip; |
| 81 | + return manager.capabilities[capability](context); |
| 82 | + }; |
| 83 | + } |
| 84 | + |
| 85 | + /** Routes package mutations using the existing automatic uv rules. */ |
| 86 | + async manage( |
| 87 | + environment: PythonEnvironment, |
| 88 | + options: PackageManagementOptions, |
| 89 | + toolToken?: CancellationToken, |
| 90 | + scope?: Uri, |
| 91 | + ): Promise<void> { |
| 92 | + throwIfCancelled(toolToken); |
| 93 | + if (!options.install?.length && !options.uninstall?.length && options.runHeadless) { |
| 94 | + return; |
| 95 | + } |
| 96 | + const support = await this.pip.capabilities[PackageCapability.Install]({ environment }); |
| 97 | + const manager = support.supported ? await this.getManager(environment, toolToken) : this.pip; |
| 98 | + return manager.manage(environment, options, toolToken, scope); |
| 99 | + } |
| 100 | + |
| 101 | + /** Refreshes packages through the selected backend. */ |
| 102 | + async refresh(environment: PythonEnvironment): Promise<void> { |
| 103 | + return (await this.getManager(environment)).refresh(environment); |
| 104 | + } |
| 105 | + |
| 106 | + /** Lists packages through the selected backend, preserving its cache behavior. */ |
| 107 | + async getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined> { |
| 108 | + return (await this.getManager(environment)).getPackages(environment, options); |
| 109 | + } |
| 110 | + |
| 111 | + /** Returns the version of the selected backend. */ |
| 112 | + async getVersion(environment: PythonEnvironment): Promise<Pep440Version | undefined> { |
| 113 | + return (await this.getManager(environment)).getVersion(environment); |
| 114 | + } |
| 115 | + |
| 116 | + /** Looks up compatible package versions through the selected backend. */ |
| 117 | + async getPackageAvailableVersions(environment: PythonEnvironment, packageName: string): Promise<Pep440Version[]> { |
| 118 | + return (await this.getManager(environment)).getPackageAvailableVersions(environment, packageName); |
| 119 | + } |
| 120 | + |
| 121 | + /** Lists dependency-root package names through the selected backend. */ |
| 122 | + async getDirectPackageNames(environment: PythonEnvironment): Promise<Set<string> | undefined> { |
| 123 | + return (await this.getManager(environment)).getDirectPackageNames(environment); |
| 124 | + } |
| 125 | + |
| 126 | + /** Releases the pip fallback owned by this registered entry point. */ |
| 127 | + dispose(): void { |
| 128 | + this.cacheSubscriptions.forEach((subscription) => subscription.dispose()); |
| 129 | + this.pip.dispose(); |
| 130 | + } |
| 131 | +} |
0 commit comments