-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a9fcf95
commit 5e98102
Showing
2 changed files
with
142 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,20 @@ | ||
commit 239734bddfc723ebe8be47ed49c0a83cee1f371f | ||
Author: Thomas Dy <[email protected]> | ||
Date: Tue Dec 24 14:34:30 2024 +0900 | ||
|
||
Add direct dedupe strategy | ||
|
||
diff --git a/packages/plugin-essentials/sources/dedupeUtils.ts b/packages/plugin-essentials/sources/dedupeUtils.ts | ||
index ec168dd..bcf2845 100644 | ||
index d29057a2..d76192ee 100644 | ||
--- a/packages/plugin-essentials/sources/dedupeUtils.ts | ||
+++ b/packages/plugin-essentials/sources/dedupeUtils.ts | ||
@@ -22,6 +22,14 @@ export enum Strategy { | ||
@@ -1,4 +1,4 @@ | ||
-import {Project, ResolveOptions, ThrowReport, Resolver, miscUtils, Descriptor, Package, Report, Cache, DescriptorHash} from '@yarnpkg/core'; | ||
+import {Project, ResolveOptions, ThrowReport, Resolver, miscUtils, Descriptor, Package, Report, Cache, DescriptorHash, Configuration} from '@yarnpkg/core'; | ||
import {formatUtils, structUtils, IdentHash, LocatorHash, MessageName, Fetcher, FetchOptions} from '@yarnpkg/core'; | ||
import micromatch from 'micromatch'; | ||
|
||
@@ -25,6 +25,14 @@ export enum Strategy { | ||
* - dependencies are never downgraded | ||
*/ | ||
HIGHEST = `highest`, | ||
|
@@ -17,18 +29,12 @@ index ec168dd..bcf2845 100644 | |
} | ||
|
||
export const acceptedStrategies = new Set(Object.values(Strategy)); | ||
@@ -80,6 +88,112 @@ const DEDUPE_ALGORITHMS: Record<Strategy, Algorithm> = { | ||
|
||
const updatedResolution = bestCandidate.locatorHash; | ||
@@ -132,6 +140,168 @@ const DEDUPE_ALGORITHMS: Record<Strategy, Algorithm> = { | ||
}); | ||
} | ||
|
||
+ const updatedPackage = project.originalPackages.get(updatedResolution); | ||
+ if (typeof updatedPackage === `undefined`) | ||
+ throw new Error(`Assertion failed: The package (${updatedResolution}) should have been registered`); | ||
+ | ||
+ if (updatedResolution === currentResolution) | ||
+ return null; | ||
+ | ||
+ return {descriptor, currentPackage, updatedPackage}; | ||
+ return [...deferredMap.values()].map(deferred => { | ||
+ return deferred.promise; | ||
+ }); | ||
+ }, | ||
+ direct: async (project, patterns, {resolver, fetcher, resolveOptions, fetchOptions}) => { | ||
|
@@ -45,7 +51,8 @@ index ec168dd..bcf2845 100644 | |
+ const directLocatorsByIdent = new Map<IdentHash, Set<LocatorHash>>(); | ||
+ for (const workspace of project.workspaces) { | ||
+ const addIdents = async (deps: Map<IdentHash, Descriptor>) => { | ||
+ for (const descriptor of deps.values()) { | ||
+ const normalizedMap = project.configuration.normalizeDependencyMap(deps); | ||
+ for (const descriptor of normalizedMap.values()) { | ||
+ const locators = locatorsByIdent.get(descriptor.identHash); | ||
+ if (typeof locators === `undefined`) | ||
+ throw new Error(`Assertion failed: The resolutions (${descriptor.identHash}) should have been registered`); | ||
|
@@ -55,78 +62,139 @@ index ec168dd..bcf2845 100644 | |
+ if (typeof pkg === `undefined`) | ||
+ throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`); | ||
+ | ||
+ return pkg.reference; | ||
+ return pkg; | ||
+ }); | ||
+ | ||
+ const candidates = await resolver.getSatisfying(descriptor, references, resolveOptions); | ||
+ | ||
+ const bestCandidate = candidates?.[0]; | ||
+ if (typeof bestCandidate === `undefined`) | ||
+ continue; | ||
+ | ||
+ miscUtils.getSetWithDefault(directLocatorsByIdent, descriptor.identHash).add(bestCandidate.locatorHash); | ||
+ try { | ||
+ // some resolvers (like the patch resolver) will throw if we don't | ||
+ // provide the resolved dependencies but we don't really care about | ||
+ // them | ||
+ const satisfying = await resolver.getSatisfying(descriptor, {}, references, resolveOptions); | ||
+ | ||
+ // if there are no results or the result list is not sorted we | ||
+ // can't know the actual "best" candidate | ||
+ if (satisfying.locators.length === 0 || !satisfying.sorted) { | ||
+ continue; | ||
+ } | ||
+ | ||
+ const bestCandidate = satisfying.locators[0]; | ||
+ miscUtils.getSetWithDefault(directLocatorsByIdent, descriptor.identHash).add(bestCandidate.locatorHash); | ||
+ } catch {} | ||
+ } | ||
+ }; | ||
+ | ||
+ await addIdents(workspace.manifest.dependencies); | ||
+ await addIdents(workspace.manifest.devDependencies); | ||
+ } | ||
+ | ||
+ return Array.from(project.storedDescriptors.values(), async descriptor => { | ||
+ if (patterns.length && !micromatch.isMatch(structUtils.stringifyIdent(descriptor), patterns)) | ||
+ return null; | ||
+ | ||
+ const deferredMap = new Map<DescriptorHash, miscUtils.Deferred<PackageUpdate>>( | ||
+ miscUtils.mapAndFilter(project.storedDescriptors.values(), descriptor => { | ||
+ // We only care about resolutions that are stored in the lockfile | ||
+ // (we shouldn't accidentally try deduping virtual packages) | ||
+ if (structUtils.isVirtualDescriptor(descriptor)) | ||
+ return miscUtils.mapAndFilter.skip; | ||
+ | ||
+ return [descriptor.descriptorHash, miscUtils.makeDeferred()]; | ||
+ }), | ||
+ ); | ||
+ | ||
+ for (const descriptor of project.storedDescriptors.values()) { | ||
+ const deferred = deferredMap.get(descriptor.descriptorHash); | ||
+ if (typeof deferred === `undefined`) | ||
+ throw new Error(`Assertion failed: The descriptor (${descriptor.descriptorHash}) should have been registered`); | ||
+ | ||
+ const currentResolution = project.storedResolutions.get(descriptor.descriptorHash); | ||
+ if (typeof currentResolution === `undefined`) | ||
+ throw new Error(`Assertion failed: The resolution (${descriptor.descriptorHash}) should have been registered`); | ||
+ | ||
+ // We only care about resolutions that are stored in the lockfile | ||
+ // (we shouldn't accidentally try deduping virtual packages) | ||
+ const currentPackage = project.originalPackages.get(currentResolution); | ||
+ if (typeof currentPackage === `undefined`) | ||
+ return null; | ||
+ | ||
+ // No need to try deduping packages that are not persisted, | ||
+ // they will be resolved again anyways | ||
+ if (!resolver.shouldPersistResolution(currentPackage, resolveOptions)) | ||
+ return null; | ||
+ | ||
+ const locators = locatorsByIdent.get(descriptor.identHash); | ||
+ if (typeof locators === `undefined`) | ||
+ throw new Error(`Assertion failed: The resolutions (${descriptor.identHash}) should have been registered`); | ||
+ | ||
+ // No need to choose when there's only one possibility | ||
+ if (locators.size === 1) | ||
+ return null; | ||
+ | ||
+ const references = [...locators].map(locatorHash => { | ||
+ const pkg = project.originalPackages.get(locatorHash); | ||
+ if (typeof pkg === `undefined`) | ||
+ throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`); | ||
+ | ||
+ return pkg.reference; | ||
+ }); | ||
+ | ||
+ let candidates = await resolver.getSatisfying(descriptor, references, resolveOptions); | ||
+ | ||
+ const directLocators = directLocatorsByIdent.get(descriptor.identHash); | ||
+ | ||
+ // If this ident is of a direct dependency, use only the candidates those | ||
+ // direct dependencies will use. If this results in no candidates, use | ||
+ // all candidates. | ||
+ if (typeof directLocators !== `undefined` && candidates !== null) { | ||
+ const filteredCandidates = candidates.filter(locator => directLocators.has(locator.locatorHash)); | ||
+ if (filteredCandidates.length > 0) { | ||
+ candidates = filteredCandidates; | ||
+ throw new Error(`Assertion failed: The package (${currentResolution}) should have been registered`); | ||
+ | ||
+ Promise.resolve().then(async () => { | ||
+ const dependencies = resolver.getResolutionDependencies(descriptor, resolveOptions); | ||
+ | ||
+ const resolvedDependencies = Object.fromEntries( | ||
+ await miscUtils.allSettledSafe( | ||
+ Object.entries(dependencies).map(async ([dependencyName, dependency]) => { | ||
+ const dependencyDeferred = deferredMap.get(dependency.descriptorHash); | ||
+ if (typeof dependencyDeferred === `undefined`) | ||
+ throw new Error(`Assertion failed: The descriptor (${dependency.descriptorHash}) should have been registered`); | ||
+ | ||
+ const dedupeResult = await dependencyDeferred.promise; | ||
+ if (!dedupeResult) | ||
+ throw new Error(`Assertion failed: Expected the dependency to have been through the dedupe process itself`); | ||
+ | ||
+ return [dependencyName, dedupeResult.updatedPackage]; | ||
+ }), | ||
+ ), | ||
+ ); | ||
+ | ||
+ if (patterns.length && !micromatch.isMatch(structUtils.stringifyIdent(descriptor), patterns)) | ||
+ return currentPackage; | ||
+ | ||
+ // No need to try deduping packages that are not persisted, | ||
+ // they will be resolved again anyways | ||
+ if (!resolver.shouldPersistResolution(currentPackage, resolveOptions)) | ||
+ return currentPackage; | ||
+ | ||
+ const candidateHashes = locatorsByIdent.get(descriptor.identHash); | ||
+ if (typeof candidateHashes === `undefined`) | ||
+ throw new Error(`Assertion failed: The resolutions (${descriptor.identHash}) should have been registered`); | ||
+ | ||
+ // No need to choose when there's only one possibility | ||
+ if (candidateHashes.size === 1) | ||
+ return currentPackage; | ||
+ | ||
+ const candidates = [...candidateHashes].map(locatorHash => { | ||
+ const pkg = project.originalPackages.get(locatorHash); | ||
+ if (typeof pkg === `undefined`) | ||
+ throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`); | ||
+ | ||
+ return pkg; | ||
+ }); | ||
+ | ||
+ let satisfying = await resolver.getSatisfying(descriptor, resolvedDependencies, candidates, resolveOptions); | ||
+ | ||
+ const directLocators = directLocatorsByIdent.get(descriptor.identHash); | ||
+ | ||
+ // If this ident is of a direct dependency, use only the candidates those | ||
+ // direct dependencies will use. If this results in no candidates, use | ||
+ // all candidates. | ||
+ if (typeof directLocators !== `undefined` && candidates !== null) { | ||
+ const filteredLocators = satisfying.locators.filter(locator => directLocators.has(locator.locatorHash)); | ||
+ if (filteredLocators.length > 0) { | ||
+ satisfying = { | ||
+ locators: filteredLocators, | ||
+ sorted: satisfying.sorted, | ||
+ }; | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ const bestCandidate = candidates?.[0]; | ||
+ if (typeof bestCandidate === `undefined`) | ||
+ return null; | ||
+ | ||
+ const updatedResolution = bestCandidate.locatorHash; | ||
+ const bestLocator = satisfying.locators?.[0]; | ||
+ if (typeof bestLocator === `undefined` || !satisfying.sorted) | ||
+ return currentPackage; | ||
+ | ||
+ const updatedPackage = project.originalPackages.get(bestLocator.locatorHash); | ||
+ if (typeof updatedPackage === `undefined`) | ||
+ throw new Error(`Assertion failed: The package (${bestLocator.locatorHash}) should have been registered`); | ||
+ | ||
+ return updatedPackage; | ||
+ }).then(async updatedPackage => { | ||
+ const resolvedPackage = await project.preparePackage(updatedPackage, {resolver, resolveOptions}); | ||
+ | ||
+ deferred.resolve({ | ||
+ descriptor, | ||
+ currentPackage, | ||
+ updatedPackage, | ||
+ resolvedPackage, | ||
+ }); | ||
+ }).catch(error => { | ||
+ deferred.reject(error); | ||
+ }); | ||
+ } | ||
+ | ||
const updatedPackage = project.originalPackages.get(updatedResolution); | ||
if (typeof updatedPackage === `undefined`) | ||
throw new Error(`Assertion failed: The package (${updatedResolution}) should have been registered`); | ||
return [...deferredMap.values()].map(deferred => { | ||
return deferred.promise; | ||
}); |