@@ -11,10 +11,11 @@ import { promisify } from 'util';
11
11
import { promises as fs , createReadStream , createWriteStream } from 'fs' ;
12
12
import { AddonConfig , loadGYPConfig , storeGYPConfig , modifyAddonGyp } from './native-addons' ;
13
13
import { ExecutableMetadata , generateRCFile } from './executable-metadata' ;
14
- import { spawnBuildCommand , ProcessEnv , pipeline , createCppJsStringDefinition } from './helpers' ;
14
+ import { spawnBuildCommand , ProcessEnv , pipeline , createCppJsStringDefinition , createCompressedBlobDefinition } from './helpers' ;
15
15
import { Readable } from 'stream' ;
16
16
import nv from '@pkgjs/nv' ;
17
17
import { fileURLToPath , URL } from 'url' ;
18
+ import { execFile } from 'child_process' ;
18
19
19
20
// Download and unpack a tarball containing the code for a specific Node.js version.
20
21
async function getNodeSourceForVersion ( range : string , dir : string , logger : Logger , retries = 2 ) : Promise < string > {
@@ -26,7 +27,8 @@ async function getNodeSourceForVersion (range: string, dir: string, logger: Logg
26
27
} catch { /* not a valid URL */ }
27
28
28
29
if ( inputIsFileUrl ) {
29
- logger . stepStarting ( `Extracting tarball from ${ range } ` ) ;
30
+ logger . stepStarting ( `Extracting tarball from ${ range } to ${ dir } ` ) ;
31
+ await fs . mkdir ( dir , { recursive : true } ) ;
30
32
await pipeline (
31
33
createReadStream ( fileURLToPath ( range ) ) ,
32
34
zlib . createGunzip ( ) ,
@@ -179,9 +181,9 @@ async function compileNode (
179
181
const nodeVersion = await getNodeVersionFromSourceDirectory ( sourcePath ) ;
180
182
if ( nodeVersion [ 0 ] > 19 || ( nodeVersion [ 0 ] === 19 && nodeVersion [ 1 ] >= 4 ) ) {
181
183
if ( process . platform !== 'win32' ) {
182
- buildArgs . unshift ( '--disable-shared-readonly-heap' ) ;
184
+ buildArgs = [ '--disable-shared-readonly-heap' , ... buildArgs ] ;
183
185
} else {
184
- buildArgs . unshift ( 'no-shared-roheap' ) ;
186
+ buildArgs = [ 'no-shared-roheap' , ... buildArgs ] ;
185
187
}
186
188
}
187
189
@@ -201,6 +203,13 @@ async function compileNode (
201
203
202
204
return path . join ( sourcePath , 'out' , 'Release' , 'node' ) ;
203
205
} else {
206
+ // On Windows, running vcbuild multiple times may result in errors
207
+ // when the source data changes in between runs.
208
+ await fs . rm ( path . join ( sourcePath , 'out' , 'Release' ) , {
209
+ recursive : true ,
210
+ force : true
211
+ } ) ;
212
+
204
213
// These defaults got things to work locally. We only include them if no
205
214
// conflicting arguments have been passed manually.
206
215
const vcbuildArgs : string [ ] = [ ...buildArgs , ...makeArgs , 'projgen' ] ;
@@ -230,6 +239,7 @@ type CompilationOptions = {
230
239
addons ?: AddonConfig [ ] ,
231
240
enableBindingsPatch ?: boolean ,
232
241
useLegacyDefaultUvLoop ?: boolean ;
242
+ useCodeCache ?: boolean ,
233
243
executableMetadata ?: ExecutableMetadata ,
234
244
preCompileHook ?: ( nodeSourceTree : string , options : CompilationOptions ) => void | Promise < void >
235
245
}
@@ -258,12 +268,12 @@ async function compileJSFileAsBinaryImpl (options: CompilationOptions, logger: L
258
268
const enableBindingsPatch = options . enableBindingsPatch ?? options . addons ?. length > 0 ;
259
269
260
270
const jsMainSource = await fs . readFile ( options . sourceFile , 'utf8' ) ;
271
+ const registerFunctions : string [ ] = [ ] ;
261
272
262
273
// We use the official embedder API for stability, which is available in all
263
274
// supported versions of Node.js.
264
275
{
265
276
const extraGypDependencies : string [ ] = [ ] ;
266
- const registerFunctions : string [ ] = [ ] ;
267
277
for ( const addon of ( options . addons || [ ] ) ) {
268
278
const addonResult = await modifyAddonGyp (
269
279
addon , nodeSourcePath , options . env || process . env , logger ) ;
@@ -290,23 +300,6 @@ async function compileJSFileAsBinaryImpl (options: CompilationOptions, logger: L
290
300
await fs . writeFile ( path . join ( nodeSourcePath , 'src' , header ) , source ) ;
291
301
}
292
302
logger . stepCompleted ( ) ;
293
-
294
- logger . stepStarting ( 'Handling main file source' ) ;
295
- let mainSource = await fs . readFile (
296
- path . join ( __dirname , '..' , 'resources' , 'main-template.cc' ) , 'utf8' ) ;
297
- mainSource = mainSource . replace ( / \b R E P L A C E _ W I T H _ E N T R Y _ P O I N T \b / g,
298
- JSON . stringify ( JSON . stringify ( `${ namespace } /${ namespace } ` ) ) ) ;
299
- mainSource = mainSource . replace ( / \b R E P L A C E _ D E C L A R E _ L I N K E D _ M O D U L E S \b / g,
300
- registerFunctions . map ( ( fn ) => `void ${ fn } (const void**,const void**);\n` ) . join ( '' ) ) ;
301
- mainSource = mainSource . replace ( / \b R E P L A C E _ D E F I N E _ L I N K E D _ M O D U L E S \b / g,
302
- registerFunctions . map ( ( fn ) => `${ fn } ,` ) . join ( '' ) ) ;
303
- mainSource = mainSource . replace ( / \b R E P L A C E _ W I T H _ M A I N _ S C R I P T _ S O U R C E _ G E T T E R \b / g,
304
- createCppJsStringDefinition ( 'GetBoxednodeMainScriptSource' , jsMainSource ) ) ;
305
- if ( options . useLegacyDefaultUvLoop ) {
306
- mainSource = `#define BOXEDNODE_USE_DEFAULT_UV_LOOP 1\n${ mainSource } ` ;
307
- }
308
- await fs . writeFile ( path . join ( nodeSourcePath , 'src' , 'node_main.cc' ) , mainSource ) ;
309
- logger . stepCompleted ( ) ;
310
303
}
311
304
312
305
logger . stepStarting ( 'Inserting custom code into Node.js source' ) ;
@@ -338,13 +331,61 @@ async function compileJSFileAsBinaryImpl (options: CompilationOptions, logger: L
338
331
logger . stepCompleted ( ) ;
339
332
}
340
333
341
- const binaryPath = await compileNode (
342
- nodeSourcePath ,
343
- extraJSSourceFiles ,
344
- options . configureArgs ,
345
- options . makeArgs ,
346
- options . env || process . env ,
347
- logger ) ;
334
+ async function writeMainFileAndCompile ( { codeCacheBlob, codeCacheMode } : {
335
+ codeCacheBlob : Uint8Array ,
336
+ codeCacheMode : 'ignore' | 'generate' | 'consume'
337
+ } ) : Promise < string > {
338
+ logger . stepStarting ( 'Handling main file source' ) ;
339
+ let mainSource = await fs . readFile (
340
+ path . join ( __dirname , '..' , 'resources' , 'main-template.cc' ) , 'utf8' ) ;
341
+ mainSource = mainSource . replace ( / \b R E P L A C E _ W I T H _ E N T R Y _ P O I N T \b / g,
342
+ JSON . stringify ( JSON . stringify ( `${ namespace } /${ namespace } ` ) ) ) ;
343
+ mainSource = mainSource . replace ( / \b R E P L A C E _ D E C L A R E _ L I N K E D _ M O D U L E S \b / g,
344
+ registerFunctions . map ( ( fn ) => `void ${ fn } (const void**,const void**);\n` ) . join ( '' ) ) ;
345
+ mainSource = mainSource . replace ( / \b R E P L A C E _ D E F I N E _ L I N K E D _ M O D U L E S \b / g,
346
+ registerFunctions . map ( ( fn ) => `${ fn } ,` ) . join ( '' ) ) ;
347
+ mainSource = mainSource . replace ( / \b R E P L A C E _ W I T H _ M A I N _ S C R I P T _ S O U R C E _ G E T T E R \b / g,
348
+ createCppJsStringDefinition ( 'GetBoxednodeMainScriptSource' , jsMainSource ) + '\n' +
349
+ await createCompressedBlobDefinition ( 'GetBoxednodeCodeCache' , codeCacheBlob ) ) ;
350
+ mainSource = mainSource . replace ( / \b B O X E D N O D E _ C O D E _ C A C H E _ M O D E \b / g,
351
+ JSON . stringify ( codeCacheMode ) ) ;
352
+ if ( options . useLegacyDefaultUvLoop ) {
353
+ mainSource = `#define BOXEDNODE_USE_DEFAULT_UV_LOOP 1\n${ mainSource } ` ;
354
+ }
355
+ await fs . writeFile ( path . join ( nodeSourcePath , 'src' , 'node_main.cc' ) , mainSource ) ;
356
+ logger . stepCompleted ( ) ;
357
+
358
+ return await compileNode (
359
+ nodeSourcePath ,
360
+ extraJSSourceFiles ,
361
+ options . configureArgs ,
362
+ options . makeArgs ,
363
+ options . env || process . env ,
364
+ logger ) ;
365
+ }
366
+
367
+ let binaryPath : string ;
368
+ if ( ! options . useCodeCache ) {
369
+ binaryPath = await writeMainFileAndCompile ( {
370
+ codeCacheBlob : new Uint8Array ( 0 ) ,
371
+ codeCacheMode : 'ignore'
372
+ } ) ;
373
+ } else {
374
+ binaryPath = await writeMainFileAndCompile ( {
375
+ codeCacheBlob : new Uint8Array ( 0 ) ,
376
+ codeCacheMode : 'generate'
377
+ } ) ;
378
+ logger . stepStarting ( 'Running code cache generation' ) ;
379
+ const codeCacheResult = await promisify ( execFile ) ( binaryPath , { encoding : 'buffer' } ) ;
380
+ if ( codeCacheResult . stdout . length === 0 ) {
381
+ throw new Error ( 'Empty code cache result' ) ;
382
+ }
383
+ logger . stepCompleted ( ) ;
384
+ binaryPath = await writeMainFileAndCompile ( {
385
+ codeCacheBlob : codeCacheResult . stdout ,
386
+ codeCacheMode : 'consume'
387
+ } ) ;
388
+ }
348
389
349
390
logger . stepStarting ( `Moving resulting binary to ${ options . targetFile } ` ) ;
350
391
await fs . mkdir ( path . dirname ( options . targetFile ) , { recursive : true } ) ;
0 commit comments