Skip to content

Commit 7007980

Browse files
committed
feat(v8)!: synchronize Rust crates with major updates
This is a breaking change, as Cargo is now a mandatory requirement for running a major update.
1 parent 0ead0a1 commit 7007980

8 files changed

Lines changed: 224 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
@@ -386,6 +386,7 @@ modifying your `PATH` environment variable).
386386
### `git node v8 major`
387387

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

@@ -397,6 +398,9 @@ Options:
397398
- `--no-version-bump`: Disable automatic bump of the `NODE_MODULE_VERSION`
398399
constant.
399400

401+
- `--cargo=/path/to/cargo`: The Cargo binary that will be executed by the shell
402+
when updating the Rust crates. Defaults to `cargo`.
403+
400404
### `git node v8 minor`
401405

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

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)