pnpm
258 строк · 8.7 Кб
1import assert from 'assert'2import path from 'path'3import util from 'util'4import { calcDepState, type DepsStateCache } from '@pnpm/calc-dep-state'5import { skippedOptionalDependencyLogger } from '@pnpm/core-loggers'6import { runPostinstallHooks } from '@pnpm/lifecycle'7import { linkBins, linkBinsOfPackages } from '@pnpm/link-bins'8import { logger } from '@pnpm/logger'9import { hardLinkDir } from '@pnpm/worker'10import { readPackageJsonFromDir, safeReadPackageJsonFromDir } from '@pnpm/read-package-json'11import { type StoreController } from '@pnpm/store-controller-types'12import { applyPatchToDir } from '@pnpm/patching.apply-patch'13import { type DependencyManifest } from '@pnpm/types'14import pDefer, { type DeferredPromise } from 'p-defer'15import pickBy from 'ramda/src/pickBy'16import runGroups from 'run-groups'17import { buildSequence, type DependenciesGraph, type DependenciesGraphNode } from './buildSequence'18
19export type { DepsStateCache }20
21export async function buildModules (22depGraph: DependenciesGraph,23rootDepPaths: string[],24opts: {25allowBuild?: (pkgName: string) => boolean26childConcurrency?: number27depsToBuild?: Set<string>28depsStateCache: DepsStateCache29extraBinPaths?: string[]30extraNodePaths?: string[]31extraEnv?: Record<string, string>32ignoreScripts?: boolean33lockfileDir: string34optional: boolean35preferSymlinkedExecutables?: boolean36rawConfig: object37unsafePerm: boolean38userAgent: string39scriptsPrependNodePath?: boolean | 'warn-only'40scriptShell?: string41shellEmulator?: boolean42sideEffectsCacheWrite: boolean43storeController: StoreController44rootModulesDir: string45hoistedLocations?: Record<string, string[]>46}47): Promise<void> {48const warn = (message: string) => {49logger.warn({ message, prefix: opts.lockfileDir })50}51// postinstall hooks52
53const buildDepOpts = {54...opts,55builtHoistedDeps: opts.hoistedLocations ? {} : undefined,56warn,57}58const chunks = buildSequence(depGraph, rootDepPaths)59const ignoredPkgs = new Set<string>()60const allowBuild = opts.allowBuild61? (pkgName: string) => {62if (opts.allowBuild!(pkgName)) return true63ignoredPkgs.add(pkgName)64return false65}66: () => true67const groups = chunks.map((chunk) => {68chunk = chunk.filter((depPath) => {69const node = depGraph[depPath]70return (node.requiresBuild || node.patchFile != null) && !node.isBuilt71})72if (opts.depsToBuild != null) {73chunk = chunk.filter((depPath) => opts.depsToBuild!.has(depPath))74}75
76return chunk.map((depPath: string) =>77async () => {78return buildDependency(depPath, depGraph, {79...buildDepOpts,80ignoreScripts: Boolean(buildDepOpts.ignoreScripts) || !allowBuild(depGraph[depPath].name),81})82}83)84})85await runGroups(opts.childConcurrency ?? 4, groups)86if (ignoredPkgs.size > 0) {87logger.info({88message: `The following dependencies have build scripts that were ignored: ${Array.from(ignoredPkgs).sort().join(', ')}`,89prefix: opts.lockfileDir,90})91}92}
93
94async function buildDependency (95depPath: string,96depGraph: DependenciesGraph,97opts: {98extraBinPaths?: string[]99extraNodePaths?: string[]100extraEnv?: Record<string, string>101depsStateCache: DepsStateCache102ignoreScripts?: boolean103lockfileDir: string104optional: boolean105preferSymlinkedExecutables?: boolean106rawConfig: object107rootModulesDir: string108scriptsPrependNodePath?: boolean | 'warn-only'109scriptShell?: string110shellEmulator?: boolean111sideEffectsCacheWrite: boolean112storeController: StoreController113unsafePerm: boolean114hoistedLocations?: Record<string, string[]>115builtHoistedDeps?: Record<string, DeferredPromise<void>>116warn: (message: string) => void117}118): Promise<void> {119const depNode = depGraph[depPath]120if (!depNode.filesIndexFile) return121if (opts.builtHoistedDeps) {122if (opts.builtHoistedDeps[depNode.depPath]) {123await opts.builtHoistedDeps[depNode.depPath].promise124return125}126opts.builtHoistedDeps[depNode.depPath] = pDefer()127}128try {129await linkBinsOfDependencies(depNode, depGraph, opts)130const isPatched = depNode.patchFile?.path != null131if (isPatched) {132applyPatchToDir({ patchedDir: depNode.dir, patchFilePath: depNode.patchFile!.path })133}134const hasSideEffects = !opts.ignoreScripts && await runPostinstallHooks({135depPath,136extraBinPaths: opts.extraBinPaths,137extraEnv: opts.extraEnv,138initCwd: opts.lockfileDir,139optional: depNode.optional,140pkgRoot: depNode.dir,141rawConfig: opts.rawConfig,142rootModulesDir: opts.rootModulesDir,143scriptsPrependNodePath: opts.scriptsPrependNodePath,144scriptShell: opts.scriptShell,145shellEmulator: opts.shellEmulator,146unsafePerm: opts.unsafePerm || false,147})148if ((isPatched || hasSideEffects) && opts.sideEffectsCacheWrite) {149try {150const sideEffectsCacheKey = calcDepState(depGraph, opts.depsStateCache, depPath, {151patchFileHash: depNode.patchFile?.hash,152isBuilt: hasSideEffects,153})154await opts.storeController.upload(depNode.dir, {155sideEffectsCacheKey,156filesIndexFile: depNode.filesIndexFile,157})158} catch (err: unknown) {159assert(util.types.isNativeError(err))160if (err && 'statusCode' in err && err.statusCode === 403) {161logger.warn({162message: `The store server disabled upload requests, could not upload ${depNode.dir}`,163prefix: opts.lockfileDir,164})165} else {166logger.warn({167error: err,168message: `An error occurred while uploading ${depNode.dir}`,169prefix: opts.lockfileDir,170})171}172}173}174} catch (err: unknown) {175assert(util.types.isNativeError(err))176if (depNode.optional) {177// TODO: add parents field to the log178const pkg = await readPackageJsonFromDir(path.join(depNode.dir)) as DependencyManifest179skippedOptionalDependencyLogger.debug({180details: err.toString(),181package: {182id: depNode.dir,183name: pkg.name,184version: pkg.version,185},186prefix: opts.lockfileDir,187reason: 'build_failure',188})189return190}191throw err192} finally {193const hoistedLocationsOfDep = opts.hoistedLocations?.[depNode.depPath]194if (hoistedLocationsOfDep) {195// There is no need to build the same package in every location.196// We just copy the built package to every location where it is present.197const currentHoistedLocation = path.relative(opts.lockfileDir, depNode.dir)198const nonBuiltHoistedDeps = hoistedLocationsOfDep?.filter((hoistedLocation) => hoistedLocation !== currentHoistedLocation)199await hardLinkDir(depNode.dir, nonBuiltHoistedDeps)200}201if (opts.builtHoistedDeps) {202opts.builtHoistedDeps[depNode.depPath].resolve()203}204}205}
206
207export async function linkBinsOfDependencies (208depNode: DependenciesGraphNode,209depGraph: DependenciesGraph,210opts: {211extraNodePaths?: string[]212optional: boolean213preferSymlinkedExecutables?: boolean214warn: (message: string) => void215}216): Promise<void> {217const childrenToLink: Record<string, string> = opts.optional218? depNode.children219: pickBy((child, childAlias) => !depNode.optionalDependencies.has(childAlias), depNode.children)220
221const binPath = path.join(depNode.dir, 'node_modules/.bin')222
223const pkgNodes = [224...Object.entries(childrenToLink)225.map(([alias, childDepPath]) => ({ alias, dep: depGraph[childDepPath] }))226.filter(({ alias, dep }) => {227if (!dep) {228// TODO: Try to reproduce this issue with a test in @pnpm/core229logger.debug({ message: `Failed to link bins of "${alias}" to "${binPath}". This is probably not an issue.` })230return false231}232return dep.hasBin && dep.installable !== false233})234.map(({ dep }) => dep),235depNode,236]237const pkgs = await Promise.all(pkgNodes238.map(async (dep) => ({239location: dep.dir,240manifest: await dep.fetchingBundledManifest?.() ?? (await safeReadPackageJsonFromDir(dep.dir) as DependencyManifest) ?? {},241}))242)243
244await linkBinsOfPackages(pkgs, binPath, {245extraNodePaths: opts.extraNodePaths,246preferSymlinkedExecutables: opts.preferSymlinkedExecutables,247})248
249// link also the bundled dependencies` bins250if (depNode.hasBundledDependencies) {251const bundledModules = path.join(depNode.dir, 'node_modules')252await linkBins(bundledModules, binPath, {253extraNodePaths: opts.extraNodePaths,254preferSymlinkedExecutables: opts.preferSymlinkedExecutables,255warn: opts.warn,256})257}258}
259