diff --git a/lib/update-v8/updateCrates.js b/lib/update-v8/updateCrates.js index 672cb3c3..ff86933b 100644 --- a/lib/update-v8/updateCrates.js +++ b/lib/update-v8/updateCrates.js @@ -1,6 +1,7 @@ import semver from 'semver'; import * as TOML from 'smol-toml'; import { promises as fs } from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { removeDirectory } from './util.js'; @@ -15,6 +16,7 @@ export default function updateCrates() { enumerateTemporalDependencies(), removeExistingCrates(), vendorCrates(), + fetchPlaceholderCrates(), buildManifest(), generateLockfile(), updateGYP(), @@ -78,15 +80,23 @@ function vendorCrates() { await fs.mkdir(nodeVendor); const subtasks = []; - const rustVersions = []; + ctx.rustVersions = []; + ctx.placeholderCrates = []; for (const dir of await fs.readdir(chromiumVendor)) { const crate = await getCrateInfo(dir); if (!crate || !ctx.temporalCrates.has(`${crate.name}@${crate.version}`)) continue; if (crate.name === 'temporal_capi') { ctx.temporalCAPIDirectory = dir; } + // Fetch replace_with placeholders from crates.io. + // This is a stop-gap fix that only works as long as the + // See https://chromium-review.googlesource.com/c/chromium/src/+/8527739 + if (crate.metadata?.gnrt?.is_placeholder) { + ctx.placeholderCrates.push({ dir, name: crate.name, version: crate.version }); + continue; + } if (crate['rust-version']) { - rustVersions.push(crate['rust-version']); + ctx.rustVersions.push(crate['rust-version']); } subtasks.push({ title: dir, @@ -100,11 +110,6 @@ function vendorCrates() { }); } - if (rustVersions.length) { - const msrv = rustVersions.map(semver.coerce).reduce((a, b) => (semver.gt(a, b) ? a : b)); - ctx.msrv = `${msrv.major}.${msrv.minor}`; - } - return task.newListr(subtasks, { concurrent: ctx.concurrent }); async function getCrateInfo(crate) { @@ -121,10 +126,75 @@ function vendorCrates() { }; } +// TODO(joyeecheung): Instead of fetching the crates from crates.io, +// generate the replacement crates from Chromium's data. e.g. +// For `replace_with = "//third_party/rust/icu4x_data:lib"` +// in gnrt_config.toml, copy the `.rs.data` files that the dependent +// crates use (`impl_!(Baked)` in their provider.rs) from +// third_party/rust/icu4x_data/src/data/common and generate a data/mod.rs in the +// upstream crate layout. +// See https://github.com/nodejs/node-core-utils/pull/1117 +function fetchPlaceholderCrates() { + return { + title: 'Fetch placeholder crates from crates.io', + skip: (ctx) => ctx.placeholderCrates.length === 0 && 'No placeholder crates', + task: async(ctx) => { + const nodeVendor = path.join(ctx.nodeDir, nodeCratesDir, 'vendor'); + const chromiumPatches = path.join(ctx.nodeDir, chromiumCratesDir, 'patches'); + for (const { dir, name } of ctx.placeholderCrates) { + try { + await fs.access(path.join(chromiumPatches, dir)); + } catch { + continue; + } + throw new Error(`Detected Chromium patches ${name} in ${chromiumPatches}/${dir}, ` + + 'the stop-gap fix of fetching crates from crates.io no longer work.'); + } + + // Vendor a temporary package that depends on on the crates to fetch them. + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'node-crates-')); + try { + const manifest = { + package: { name: 'placeholder_crates', version: '0.0.0', edition: '2021' }, + dependencies: Object.fromEntries(ctx.placeholderCrates.map(({ name, version }) => ( + [name, { version: `=${version}`, 'default-features': false }] + ))) + }; + await fs.writeFile(path.join(tmpDir, 'Cargo.toml'), TOML.stringify(manifest)); + await fs.mkdir(path.join(tmpDir, 'src')); + await fs.writeFile(path.join(tmpDir, 'src/lib.rs'), ''); + const vendorDir = path.join(tmpDir, 'vendor'); + await forceRunAsync(ctx.cargo, ['vendor', '--quiet', '--versioned-dirs', vendorDir], { + ignoreFailure: false, + spawnArgs: { cwd: tmpDir } + }); + + for (const { dir, name, version } of ctx.placeholderCrates) { + const dest = path.join(nodeVendor, dir); + await fs.cp(path.join(vendorDir, `${name}-${version}`), dest, { recursive: true }); + await fs.writeFile(path.join(dest, '.cargo-checksum.json'), '{"files":{}}\n'); + const crate = TOML.parse(await fs.readFile(path.join(dest, 'Cargo.toml'), 'utf8')); + if (crate.package['rust-version']) { + ctx.rustVersions.push(crate.package['rust-version']); + } + } + } finally { + await removeDirectory(tmpDir); + } + } + }; +} + function buildManifest() { return { title: 'Build new manifest', task: async(ctx) => { + if (ctx.rustVersions.length) { + const msrv = ctx.rustVersions.map(semver.coerce) + .reduce((a, b) => (semver.gt(a, b) ? a : b)); + ctx.msrv = `${msrv.major}.${msrv.minor}`; + } + const sourceManifest = TOML.parse( await fs.readFile(path.join(ctx.nodeDir, chromiumCratesDir, 'Cargo.toml'), 'utf8') );