Skip to content

Commit 78e1dbb

Browse files
authored
feat(v8)!: synchronize Rust crates with major updates (#1117)
1 parent c3b9dc6 commit 78e1dbb

8 files changed

Lines changed: 249 additions & 1 deletion

File tree

components/git/v8.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export function builder(yargs) {
3131
describe: 'Update dependencies concurrently',
3232
default: true,
3333
});
34+
yargs.option('cargo', {
35+
describe: 'The cargo binary that will be executed when updating the Rust crates',
36+
default: 'cargo',
37+
});
3438
}
3539
})
3640
.command({

docs/git-node.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ modifying your `PATH` environment variable).
388388
### `git node v8 major`
389389

390390
- Replaces `deps/v8` with a newer major version.
391+
- Synchronizes the manifest and vendored Rust crates in `deps/crates`.
391392
- Resets the embedder version number to `-node.0`.
392393
- Bumps `NODE_MODULE_VERSION` according to the [Node.js ABI version registry][].
393394

@@ -399,6 +400,9 @@ Options:
399400
- `--no-version-bump`: Disable automatic bump of the `NODE_MODULE_VERSION`
400401
constant.
401402

403+
- `--cargo=/path/to/cargo`: The Cargo binary that will be executed by the shell
404+
when updating the Rust crates. Defaults to `cargo`.
405+
402406
### `git node v8 minor`
403407

404408
Compare current V8 version with latest upstream of the same major. Applies a

lib/update-v8/common.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import assert from 'node:assert';
12
import path from 'node:path';
23
import { promises as fs } from 'node:fs';
34

45
import { getNodeV8Version } from './util.js';
6+
import { forceRunAsync } from '../run.js';
57

68
export function getV8Version(label) {
79
return {
@@ -12,6 +14,27 @@ export function getV8Version(label) {
1214
};
1315
};
1416

17+
export function checkCargoAvailable() {
18+
return {
19+
title: 'Check Cargo exists in environment',
20+
task: async(ctx) => {
21+
try {
22+
const output = await forceRunAsync(ctx.cargo, ['--version'], {
23+
ignoreFailure: false,
24+
captureStdout: true,
25+
});
26+
assert(output.startsWith('cargo'));
27+
} catch (cause) {
28+
let error = 'Unable to determine cargo version.';
29+
if (ctx.cargo === 'cargo') {
30+
error += '\nYou may need to specify the path to your cargo binary with `--cargo`.';
31+
}
32+
throw new Error(error, { cause });
33+
}
34+
}
35+
};
36+
}
37+
1538
export async function checkCwd(ctx) {
1639
let isNode = false;
1740
try {

lib/update-v8/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ export const v8Deps = [
154154
},
155155
since: 138
156156
},
157+
{
158+
name: 'rust',
159+
repo: 'third_party/rust',
160+
since: 139
161+
},
157162
{
158163
name: 'llvm-libc',
159164
repo: 'third_party/llvm-libc/src',

lib/update-v8/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import { Listr } from 'listr2';
22

3+
import { checkCargoAvailable } from './common.js';
34
import { checkOptions, doBackport } from './backport.js';
45
import updateVersionNumbers from './updateVersionNumbers.js';
56
import commitUpdate from './commitUpdate.js';
67
import majorUpdate from './majorUpdate.js';
78
import minorUpdate from './minorUpdate.js';
89
import updateDeps from './deps.js';
10+
import updateCrates from './updateCrates.js';
911
import updateV8Clone from './updateV8Clone.js';
1012

1113
export function major(options) {
1214
const tasks = new Listr(
13-
[updateV8Clone(), majorUpdate(), commitUpdate(), updateVersionNumbers()],
15+
[
16+
checkCargoAvailable(),
17+
updateV8Clone(),
18+
majorUpdate(),
19+
commitUpdate(),
20+
updateCrates(),
21+
updateVersionNumbers()
22+
],
1423
getOptions(options)
1524
);
1625
return tasks.run(options);

lib/update-v8/updateCrates.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import semver from 'semver';
2+
import * as TOML from 'smol-toml';
3+
import { promises as fs } from 'node:fs';
4+
import path from 'node:path';
5+
6+
import { removeDirectory } from './util.js';
7+
import { forceRunAsync } from '../run.js';
8+
9+
export default function updateCrates() {
10+
return {
11+
title: 'Update Rust crates',
12+
skip: (ctx) => ctx.newVersion.majorMinor < 139,
13+
task: (ctx, task) => {
14+
return task.newListr([
15+
enumerateTemporalDependencies(),
16+
removeExistingCrates(),
17+
vendorCrates(),
18+
buildManifest(),
19+
generateLockfile(),
20+
updateGYP(),
21+
commitChanges()
22+
]);
23+
}
24+
};
25+
}
26+
27+
const chromiumCratesDir = 'deps/v8/third_party/rust/chromium_crates_io';
28+
const nodeCratesDir = 'deps/crates';
29+
30+
function enumerateTemporalDependencies() {
31+
return {
32+
title: 'Enumerate Temporal dependency tree',
33+
task: async(ctx) => {
34+
const tree = await forceRunAsync(
35+
ctx.cargo,
36+
['tree', '-Z', 'bindeps', '--package', 'temporal_capi', '--prefix', 'none'],
37+
{
38+
ignoreFailure: false,
39+
captureStdout: true,
40+
spawnArgs: { cwd: path.join(ctx.nodeDir, chromiumCratesDir) }
41+
}
42+
);
43+
const crates = new Set();
44+
for (const crate of tree.trimEnd().split('\n').sort()) {
45+
const [name, version] = crate.split(' ', 2);
46+
crates.add(`${name}@${version.substring(1)}`);
47+
}
48+
ctx.temporalCrates = crates;
49+
},
50+
};
51+
}
52+
53+
function removeExistingCrates() {
54+
return {
55+
title: 'Remove existing crates',
56+
task: (ctx) => removeDirectory(path.join(ctx.nodeDir, nodeCratesDir, 'vendor'))
57+
};
58+
}
59+
60+
// Note that the crates vendored in third_party/rust/chromium_crates_io already have any custom
61+
// Chromium patches applied by gnrt, so we don't need to worry about patching them ourselves.
62+
function vendorCrates() {
63+
return {
64+
title: 'Copy vendored crates',
65+
task: async(ctx, task) => {
66+
const chromiumVendor = path.join(ctx.nodeDir, chromiumCratesDir, 'vendor');
67+
const nodeVendor = path.join(ctx.nodeDir, nodeCratesDir, 'vendor');
68+
await fs.mkdir(nodeVendor);
69+
70+
const subtasks = [];
71+
const rustVersions = [];
72+
for (const dir of await fs.readdir(chromiumVendor)) {
73+
const crate = await getCrateInfo(dir);
74+
if (!crate || !ctx.temporalCrates.has(`${crate.name}@${crate.version}`)) continue;
75+
if (crate.name === 'temporal_capi') {
76+
ctx.temporalCAPIDirectory = dir;
77+
}
78+
if (crate['rust-version']) {
79+
rustVersions.push(crate['rust-version']);
80+
}
81+
subtasks.push({
82+
title: dir,
83+
task: async(ctx) => {
84+
await fs.cp(
85+
path.join(chromiumVendor, dir),
86+
path.join(nodeVendor, dir),
87+
{ recursive: true }
88+
);
89+
}
90+
});
91+
}
92+
93+
if (rustVersions.length) {
94+
const msrv = rustVersions.map(semver.coerce).reduce((a, b) => (semver.gt(a, b) ? a : b));
95+
ctx.msrv = `${msrv.major}.${msrv.minor}`;
96+
}
97+
98+
return task.newListr(subtasks, { concurrent: ctx.concurrent });
99+
100+
async function getCrateInfo(crate) {
101+
try {
102+
const manifest = TOML.parse(
103+
await fs.readFile(path.join(chromiumVendor, crate, 'Cargo.toml'), 'utf8')
104+
);
105+
return manifest.package;
106+
} catch {
107+
return null;
108+
}
109+
}
110+
}
111+
};
112+
}
113+
114+
function buildManifest() {
115+
return {
116+
title: 'Build new manifest',
117+
task: async(ctx) => {
118+
const sourceManifest = TOML.parse(
119+
await fs.readFile(path.join(ctx.nodeDir, chromiumCratesDir, 'Cargo.toml'), 'utf8')
120+
);
121+
122+
const header =
123+
'# This manifest is generated by node-core-utils. Do not modify it directly.\n\n';
124+
const manifest = {
125+
package: {
126+
edition: sourceManifest.package.edition,
127+
name: 'node_crates',
128+
version: `${ctx.newVersion.major}.${ctx.newVersion.minor}.${ctx.newVersion.build}`
129+
},
130+
lib: {
131+
'crate-type': ['staticlib']
132+
},
133+
dependencies: {}
134+
};
135+
if (ctx.msrv) {
136+
manifest.package['rust-version'] = ctx.msrv;
137+
}
138+
for (const crate of ctx.temporalCrates) {
139+
const name = crate.split('@', 1)[0];
140+
if (name === 'temporal_capi' || typeof sourceManifest.dependencies[name] === 'object') {
141+
manifest.dependencies[name] = sourceManifest.dependencies[name];
142+
}
143+
}
144+
145+
const cargoTOML = header + TOML.stringify(manifest);
146+
await fs.writeFile(path.join(ctx.nodeDir, nodeCratesDir, 'Cargo.toml'), cargoTOML);
147+
},
148+
};
149+
}
150+
151+
function generateLockfile() {
152+
return {
153+
title: 'Generate lockfile',
154+
task: (ctx) => forceRunAsync(ctx.cargo, ['generate-lockfile', '--quiet', '--offline'], {
155+
ignoreFailure: false,
156+
spawnArgs: { cwd: path.join(ctx.nodeDir, nodeCratesDir) }
157+
})
158+
};
159+
}
160+
161+
function updateGYP() {
162+
return {
163+
title: 'Update include path in crates.gyp',
164+
task: async(ctx, task) => {
165+
const filePath = path.join(ctx.nodeDir, nodeCratesDir, 'crates.gyp');
166+
let gyp = await fs.readFile(filePath, 'utf8');
167+
const [definition, currentDirectory] = gyp.match(/'temporal_capi_dir': '(.+?)'/);
168+
if (currentDirectory === ctx.temporalCAPIDirectory) {
169+
task.skip('crates.gyp already up-to-date');
170+
return;
171+
}
172+
gyp = gyp.replace(definition, `'temporal_capi_dir': '${ctx.temporalCAPIDirectory}'`);
173+
await fs.writeFile(filePath, gyp);
174+
}
175+
};
176+
}
177+
178+
function commitChanges() {
179+
return {
180+
title: 'Commit changes',
181+
task: async(ctx) => {
182+
await ctx.execGitNode('add', ['--force', 'deps/crates']);
183+
await ctx.execGitNode(
184+
'commit',
185+
['-m', `deps: update Rust crates for V8 ${ctx.newVersion}`]
186+
);
187+
}
188+
};
189+
}

npm-shrinkwrap.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"ora": "^9.0.0",
5656
"replace-in-file": "^8.3.0",
5757
"semver": "^7.7.1",
58+
"smol-toml": "^1.7.0",
5859
"undici": "^8.3.0",
5960
"which": "^7.0.0",
6061
"yargs": "^18.0.0"

0 commit comments

Comments
 (0)