Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: resolve dependencies #5267

Merged
merged 5 commits into from Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
226 changes: 157 additions & 69 deletions packages/resolve-dependencies/src/resolveDependencies.ts
Expand Up @@ -41,6 +41,7 @@ import {
import * as dp from 'dependency-path'
import exists from 'path-exists'
import isEmpty from 'ramda/src/isEmpty'
import zipWith from 'ramda/src/zipWith'
import semver from 'semver'
import encodePkgId from './encodePkgId'
import getNonDevWantedDependencies, { WantedDependency } from './getNonDevWantedDependencies'
Expand Down Expand Up @@ -218,7 +219,7 @@ export interface ResolvedPackage {

type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'depPath' | 'rootDir'>

type ParentPkgAliases = Record<string, PkgAddress | true>
export type ParentPkgAliases = Record<string, PkgAddress | true>

interface ResolvedDependenciesOptions {
currentDepth: number
Expand All @@ -239,44 +240,37 @@ type PostponedResolutionFunction = (preferredVersions: PreferredVersions, parent
resolvedPeers: ResolvedPeers
}>

export async function resolveRootDependencies (
ctx: ResolutionContext,
preferredVersions: PreferredVersions,
wantedDependencies: Array<WantedDependency & { updateDepth?: number }>,
options: ResolvedDependenciesOptions
): Promise<Array<PkgAddress | LinkedDependency>> {
const pkgAddresses: Array<PkgAddress | LinkedDependency> = []
const parentPkgAliases: ParentPkgAliases = {}
for (const wantedDep of wantedDependencies) {
if (wantedDep.alias) {
parentPkgAliases[wantedDep.alias] = true
}
}
while (true) {
const result = await resolveDependencies(ctx, preferredVersions, wantedDependencies, {
...options,
parentPkgAliases,
})
pkgAddresses.push(...result.pkgAddresses)
if (!ctx.autoInstallPeers) break
for (const pkgAddress of result.pkgAddresses) {
parentPkgAliases[pkgAddress.alias] = true
}
for (const missingPeerName of Object.keys(result.missingPeers ?? {})) {
parentPkgAliases[missingPeerName] = true
}
// All the missing peers should get installed in the root.
// Otherwise, pending nodes will not work.
// even those peers should be hoisted that are not autoinstalled
for (const [resolvedPeerName, resolvedPeerAddress] of Object.entries(result.resolvedPeers ?? {})) {
if (!parentPkgAliases[resolvedPeerName]) {
pkgAddresses.push(resolvedPeerAddress)
export async function resolveRootDependencies (importers: ImporterToResolve[]): Promise<Array<Array<PkgAddress | LinkedDependency>>> {
const pkgAddressesByImportersWithoutPeers = await resolveDependenciesOfImporters(importers)
return Promise.all(zipWith(async (importerResolutionResult, { ctx, parentPkgAliases, preferredVersions, options }) => {
const pkgAddresses = importerResolutionResult.pkgAddresses
if (!ctx.autoInstallPeers) return pkgAddresses
while (true) {
for (const pkgAddress of importerResolutionResult.pkgAddresses) {
parentPkgAliases[pkgAddress.alias] = true
}
for (const missingPeerName of Object.keys(importerResolutionResult.missingPeers ?? {})) {
parentPkgAliases[missingPeerName] = true
}
// All the missing peers should get installed in the root.
// Otherwise, pending nodes will not work.
// even those peers should be hoisted that are not autoinstalled
for (const [resolvedPeerName, resolvedPeerAddress] of Object.entries(importerResolutionResult.resolvedPeers ?? {})) {
if (!parentPkgAliases[resolvedPeerName]) {
pkgAddresses.push(resolvedPeerAddress)
}
}
if (!Object.keys(importerResolutionResult.missingPeers).length) break
const wantedDependencies = getNonDevWantedDependencies({ dependencies: importerResolutionResult.missingPeers })

importerResolutionResult = await resolveDependencies(ctx, preferredVersions, wantedDependencies, {
...options,
parentPkgAliases,
})
pkgAddresses.push(...importerResolutionResult.pkgAddresses)
}
if (!Object.keys(result.missingPeers).length) break
wantedDependencies = getNonDevWantedDependencies({ dependencies: result.missingPeers })
}
return pkgAddresses
return pkgAddresses
}, pkgAddressesByImportersWithoutPeers, importers))
}

interface ResolvedDependenciesResult {
Expand All @@ -285,31 +279,119 @@ interface ResolvedDependenciesResult {
resolvedPeers: ResolvedPeers
}

export interface ImporterToResolve {
ctx: ResolutionContext
preferredVersions: PreferredVersions
parentPkgAliases: ParentPkgAliases
wantedDependencies: Array<WantedDependency & { updateDepth?: number }>
options: Omit<ResolvedDependenciesOptions, 'parentPkgAliases'>
}

export async function resolveDependenciesOfImporters (
importers: ImporterToResolve[]
): Promise<ResolvedDependenciesResult[]> {
const extendedWantedDepsByImporters = importers.map(({ ctx, wantedDependencies, options }) => getDepsToResolve(wantedDependencies, ctx.wantedLockfile, {
preferredDependencies: options.preferredDependencies,
prefix: ctx.prefix,
proceed: options.proceed || ctx.forceFullResolution,
registries: ctx.registries,
resolvedDependencies: options.resolvedDependencies,
}))
const resolveResults = await Promise.all(
zipWith(async (extendedWantedDeps, importer) => {
const postponedResolutionsQueue: PostponedResolutionFunction[] = []
const pkgAddresses: PkgAddress[] = []
;(await Promise.all(
extendedWantedDeps.map((extendedWantedDep) => resolveDependenciesOfDependency(
importer.ctx,
importer.preferredVersions,
{ ...importer.options, parentPkgAliases: importer.parentPkgAliases },
extendedWantedDep
))
)).forEach(({ resolveDependencyResult, postponedResolution }) => {
if (resolveDependencyResult) {
pkgAddresses.push(resolveDependencyResult as PkgAddress)
}
if (postponedResolution) {
postponedResolutionsQueue.push(postponedResolution)
}
})
return { pkgAddresses, postponedResolutionsQueue }
}, extendedWantedDepsByImporters, importers)
)
return Promise.all(zipWith(async (importer, { pkgAddresses, postponedResolutionsQueue }) => {
const newPreferredVersions = { ...importer.preferredVersions }
const newParentPkgAliases = { ...importer.parentPkgAliases }
for (const pkgAddress of pkgAddresses) {
if (newParentPkgAliases[pkgAddress.alias] !== true) {
newParentPkgAliases[pkgAddress.alias] = pkgAddress
}
if (pkgAddress.updated) {
importer.ctx.updatedSet.add(pkgAddress.alias)
}
const resolvedPackage = importer.ctx.resolvedPackagesByDepPath[pkgAddress.depPath]
if (!resolvedPackage) continue // This will happen only with linked dependencies
if (!newPreferredVersions[resolvedPackage.name]) {
newPreferredVersions[resolvedPackage.name] = {}
}
newPreferredVersions[resolvedPackage.name][resolvedPackage.version] = 'version'
}
const childrenResults = await Promise.all(
postponedResolutionsQueue.map(
async (postponedResolution) => postponedResolution(newPreferredVersions, newParentPkgAliases)
)
)
if (!importer.ctx.autoInstallPeers) {
return {
missingPeers: {},
pkgAddresses,
resolvedPeers: {},
}
}
const allMissingPeers = mergePkgsDeps(
[
...pkgAddresses,
...childrenResults,
].map(({ missingPeers }) => missingPeers).filter(Boolean)
)
return {
missingPeers: allMissingPeers,
pkgAddresses,
resolvedPeers: [...pkgAddresses, ...childrenResults].reduce((acc, { resolvedPeers }) => Object.assign(acc, resolvedPeers), {}),
}
}, importers, resolveResults))
}

export async function resolveDependencies (
ctx: ResolutionContext,
preferredVersions: PreferredVersions,
wantedDependencies: Array<WantedDependency & { updateDepth?: number }>,
options: ResolvedDependenciesOptions
): Promise<ResolvedDependenciesResult> {
const postponedResolutionsQueue: PostponedResolutionFunction[] = []
const extendedWantedDeps = getDepsToResolve(wantedDependencies, ctx.wantedLockfile, {
preferredDependencies: options.preferredDependencies,
prefix: ctx.prefix,
proceed: options.proceed || ctx.forceFullResolution,
registries: ctx.registries,
resolvedDependencies: options.resolvedDependencies,
})
const pkgAddresses = (
await Promise.all(
extendedWantedDeps.map(async (extendedWantedDep) => resolveDependenciesOfDependency(
postponedResolutionsQueue,
ctx,
preferredVersions,
options,
extendedWantedDep
))
)
).filter(Boolean) as PkgAddress[]
const postponedResolutionsQueue: PostponedResolutionFunction[] = []
const pkgAddresses: PkgAddress[] = []
;(await Promise.all(
extendedWantedDeps.map((extendedWantedDep) => resolveDependenciesOfDependency(
ctx,
preferredVersions,
options,
extendedWantedDep
))
)).forEach(({ resolveDependencyResult, postponedResolution }) => {
if (resolveDependencyResult) {
pkgAddresses.push(resolveDependencyResult as PkgAddress)
}
if (postponedResolution) {
postponedResolutionsQueue.push(postponedResolution)
}
})
const newPreferredVersions = { ...preferredVersions }
const newParentPkgAliases = { ...options.parentPkgAliases }
for (const pkgAddress of pkgAddresses) {
Expand Down Expand Up @@ -373,13 +455,17 @@ interface ExtendedWantedDependency {
wantedDependency: WantedDependency & { updateDepth?: number }
}

interface ResolveDependenciesOfDependency {
postponedResolution?: PostponedResolutionFunction
resolveDependencyResult: ResolveDependencyResult
}

async function resolveDependenciesOfDependency (
postponedResolutionsQueue: PostponedResolutionFunction[],
ctx: ResolutionContext,
preferredVersions: PreferredVersions,
options: ResolvedDependenciesOptions,
extendedWantedDep: ExtendedWantedDependency
) {
): Promise<ResolveDependenciesOfDependency> {
const updateDepth = typeof extendedWantedDep.wantedDependency.updateDepth === 'number'
? extendedWantedDep.wantedDependency.updateDepth
: options.updateDepth
Expand Down Expand Up @@ -413,7 +499,7 @@ async function resolveDependenciesOfDependency (
}
const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts)

if (resolveDependencyResult == null) return null
if (resolveDependencyResult == null) return { resolveDependencyResult: null }
if (resolveDependencyResult.isLinkedDependency) {
ctx.dependenciesTree[resolveDependencyResult.pkgId] = {
children: {},
Expand All @@ -424,24 +510,24 @@ async function resolveDependenciesOfDependency (
version: resolveDependencyResult.version,
},
}
return resolveDependencyResult
return { resolveDependencyResult }
}
if (!resolveDependencyResult.isNew) return resolveDependencyResult

postponedResolutionsQueue.push(async (preferredVersions, parentPkgAliases) =>
resolveChildren(
ctx,
resolveDependencyResult,
parentPkgAliases,
extendedWantedDep.infoFromLockfile?.dependencyLockfile,
options.workspacePackages,
options.currentDepth,
updateDepth,
preferredVersions
)
)
if (!resolveDependencyResult.isNew) return { resolveDependencyResult }

return resolveDependencyResult
return {
resolveDependencyResult,
postponedResolution: async (preferredVersions, parentPkgAliases) =>
resolveChildren(
ctx,
resolveDependencyResult,
parentPkgAliases,
extendedWantedDep.infoFromLockfile?.dependencyLockfile,
options.workspacePackages,
options.currentDepth,
updateDepth,
preferredVersions
),
}
}

async function resolveChildren (
Expand Down Expand Up @@ -689,11 +775,13 @@ interface ResolveDependencyOptions {
workspacePackages?: WorkspacePackages
}

type ResolveDependencyResult = PkgAddress | LinkedDependency | null

async function resolveDependency (
wantedDependency: WantedDependency,
ctx: ResolutionContext,
options: ResolveDependencyOptions
): Promise<PkgAddress | LinkedDependency | null> {
): Promise<ResolveDependencyResult> {
const currentPkg = options.currentPkg ?? {}

const currentLockfileContainsTheDep = currentPkg.depPath
Expand Down
28 changes: 17 additions & 11 deletions packages/resolve-dependencies/src/resolveDependencyTree.ts
Expand Up @@ -7,7 +7,9 @@ import {
ReadPackageHook,
Registries,
} from '@pnpm/types'
import fromPairs from 'ramda/src/fromPairs'
import partition from 'ramda/src/partition'
import zipObj from 'ramda/src/zipObj'
import { WantedDependency } from './getNonDevWantedDependencies'
import {
createNodeId,
Expand All @@ -17,6 +19,8 @@ import {
ChildrenByParentDepPath,
DependenciesTree,
LinkedDependency,
ImporterToResolve,
ParentPkgAliases,
PendingNode,
PkgAddress,
resolveRootDependencies,
Expand Down Expand Up @@ -85,8 +89,6 @@ export default async function<T> (
importers: Array<ImporterToResolveGeneric<T>>,
opts: ResolveDependenciesOptions
) {
const directDepsByImporterId = {} as {[id: string]: Array<PkgAddress | LinkedDependency>}

const wantedToBeSkippedPackageIds = new Set<string>()
const ctx = {
autoInstallPeers: opts.autoInstallPeers === true,
Expand Down Expand Up @@ -119,7 +121,7 @@ export default async function<T> (
appliedPatches: new Set<string>(),
}

await Promise.all(importers.map(async (importer) => {
const resolveArgs: ImporterToResolve[] = importers.map((importer) => {
const projectSnapshot = opts.wantedLockfile.importers[importer.id]
// This array will only contain the dependencies that should be linked in.
// The already linked-in dependencies will not be added.
Expand All @@ -144,7 +146,6 @@ export default async function<T> (
depPath: importer.id,
rootDir: importer.rootDir,
},
parentPkgAliases: {},
proceed,
resolvedDependencies: {
...projectSnapshot.dependencies,
Expand All @@ -154,13 +155,18 @@ export default async function<T> (
updateDepth: -1,
workspacePackages: opts.workspacePackages,
}
directDepsByImporterId[importer.id] = await resolveRootDependencies(
resolveCtx,
importer.preferredVersions ?? {},
importer.wantedDependencies,
resolveOpts
)
}))
return {
ctx: resolveCtx,
parentPkgAliases: fromPairs(
importer.wantedDependencies.filter(({ alias }) => alias).map(({ alias }) => [alias, true])
) as ParentPkgAliases,
preferredVersions: importer.preferredVersions ?? {},
wantedDependencies: importer.wantedDependencies,
options: resolveOpts,
}
})
const pkgAddressesByImporters = await resolveRootDependencies(resolveArgs)
const directDepsByImporterId = zipObj(importers.map(({ id }) => id), pkgAddressesByImporters)

ctx.pendingNodes.forEach((pendingNode) => {
ctx.dependenciesTree[pendingNode.nodeId] = {
Expand Down