@@ -588,30 +588,8 @@ struct PackagingPlanner {
588588 }
589589 packageInputs. append ( wasm)
590590
591- // NOTE: The imports are parsed from the product artifact instead of the final .wasm
592- // file because wasm-opt removes unreferenced imports, and the presence of an import
593- // decides how the JavaScript glue code is generated.
594591 let wasmImportsPath = self . wasmImportsPath
595- let wasmImportsTask = make. addTask (
596- inputFiles: [ selfPath, wasmProductArtifact] ,
597- inputTasks: [ intermediatesDirTask] ,
598- output: wasmImportsPath
599- ) {
600- let metadata = try parseImports (
601- moduleBytes: try Data ( contentsOf: URL ( fileURLWithPath: $1. resolve ( path: wasmProductArtifact) . path) )
602- )
603- let jsonEncoder = JSONEncoder ( )
604- jsonEncoder. outputFormatting = . prettyPrinted
605- let jsonData = try jsonEncoder. encode ( metadata)
606- let outputPath = $1. resolve ( path: $0. output)
607- // Every task consuming the imports depends on this file, so leave it untouched
608- // when the imports are unchanged. Rewriting it would give it a newer timestamp
609- // and re-run them (including `npm install`) for unrelated Swift source changes.
610- if let lastImports = try ? Data ( contentsOf: outputPath) , lastImports == jsonData {
611- return
612- }
613- try system. writeFile ( atPath: outputPath. path, content: jsonData)
614- }
592+ let wasmImportsTask = planWasmImports ( make: & make, intermediatesDirTask: intermediatesDirTask)
615593
616594 packageInputs. append ( wasmImportsTask)
617595
@@ -758,30 +736,117 @@ struct PackagingPlanner {
758736 )
759737 }
760738
739+ /// Plan the shared npm install for a directory that hosts several per-runner test
740+ /// bundles as subdirectories. Node resolves `node_modules` by walking up the directory
741+ /// tree, so a single install here serves every runner underneath, avoiding one install
742+ /// per test target.
743+ func planSharedNodeModules( make: inout MiniMake ) throws -> MiniMake . TaskKey {
744+ let outputDirTask = make. addTask (
745+ inputFiles: [ selfPath] ,
746+ output: outputDir,
747+ attributes: [ . silent]
748+ ) {
749+ try system. createDirectory ( atPath: $1. resolve ( path: $0. output) . path)
750+ }
751+ let intermediatesDirTask = make. addTask (
752+ inputFiles: [ selfPath] ,
753+ output: intermediatesDir,
754+ attributes: [ . silent]
755+ ) {
756+ try system. createDirectory ( atPath: $1. resolve ( path: $0. output) . path)
757+ }
758+ let wasmImportsTask = planWasmImports ( make: & make, intermediatesDirTask: intermediatesDirTask)
759+ let packageJsonTask = planCopyTemplateFile (
760+ make: & make,
761+ file: " Plugins/PackageToJS/Templates/package.json " ,
762+ output: " package.json " ,
763+ outputDirTask: outputDirTask,
764+ wasmImportsTask: wasmImportsTask,
765+ inputFiles: [ ] ,
766+ inputTasks: [ ]
767+ )
768+ return planNpmInstall (
769+ make: & make,
770+ intermediatesDirTask: intermediatesDirTask,
771+ packageJsonTask: packageJsonTask
772+ )
773+ }
774+
775+ /// Plan the task parsing the imports of the product .wasm file
776+ ///
777+ /// NOTE: The imports are parsed from the product artifact instead of the final .wasm
778+ /// file because wasm-opt removes unreferenced imports, and the presence of an import
779+ /// decides how the JavaScript glue code is generated.
780+ private func planWasmImports(
781+ make: inout MiniMake ,
782+ intermediatesDirTask: MiniMake . TaskKey
783+ ) -> MiniMake . TaskKey {
784+ make. addTask (
785+ inputFiles: [ selfPath, wasmProductArtifact] ,
786+ inputTasks: [ intermediatesDirTask] ,
787+ output: wasmImportsPath
788+ ) {
789+ let metadata = try parseImports (
790+ moduleBytes: try Data ( contentsOf: URL ( fileURLWithPath: $1. resolve ( path: wasmProductArtifact) . path) )
791+ )
792+ let jsonEncoder = JSONEncoder ( )
793+ jsonEncoder. outputFormatting = . prettyPrinted
794+ let jsonData = try jsonEncoder. encode ( metadata)
795+ let outputPath = $1. resolve ( path: $0. output)
796+ // Every task consuming the imports depends on this file, so leave it untouched
797+ // when the imports are unchanged. Rewriting it would give it a newer timestamp
798+ // and re-run them (including `npm install`) for unrelated Swift source changes.
799+ if let lastImports = try ? Data ( contentsOf: outputPath) , lastImports == jsonData {
800+ return
801+ }
802+ try system. writeFile ( atPath: outputPath. path, content: jsonData)
803+ }
804+ }
805+
806+ private func planNpmInstall(
807+ make: inout MiniMake ,
808+ intermediatesDirTask: MiniMake . TaskKey ,
809+ packageJsonTask: MiniMake . TaskKey
810+ ) -> MiniMake . TaskKey {
811+ make. addTask (
812+ inputFiles: [
813+ selfPath,
814+ outputDir. appending ( path: " package.json " ) ,
815+ ] ,
816+ inputTasks: [ intermediatesDirTask, packageJsonTask] ,
817+ output: intermediatesDir. appending ( path: " npm-install.stamp " )
818+ ) {
819+ try system. npmInstall ( packageDir: $1. resolve ( path: outputDir) . path)
820+ try system. writeFile ( atPath: $1. resolve ( path: $0. output) . path, content: Data ( ) )
821+ }
822+ }
823+
761824 /// Construct the test build plan and return the root task key
825+ ///
826+ /// - Parameter installNodeModules: whether to install the test harness's npm
827+ /// dependencies into this bundle's directory. Pass `false` when several runners share
828+ /// a single `node_modules` planned once via `planSharedNodeModules`.
762829 func planTestBuild(
763- make: inout MiniMake
830+ make: inout MiniMake ,
831+ installNodeModules: Bool = true
764832 ) throws -> ( rootTask: MiniMake . TaskKey , binDir: BuildPath ) {
765833 var ( allTasks, outputDirTask, intermediatesDirTask, packageJsonTask, wasmImportsTask) = try planBuildInternal (
766834 make: & make,
767835 noOptimize: false ,
768836 debugInfoFormat: . dwarf
769837 )
770838
771- // Install npm dependencies used in the test harness
772- allTasks. append (
773- make. addTask (
774- inputFiles: [
775- selfPath,
776- outputDir. appending ( path: " package.json " ) ,
777- ] ,
778- inputTasks: [ intermediatesDirTask, packageJsonTask] ,
779- output: intermediatesDir. appending ( path: " npm-install.stamp " )
780- ) {
781- try system. npmInstall ( packageDir: $1. resolve ( path: outputDir) . path)
782- try system. writeFile ( atPath: $1. resolve ( path: $0. output) . path, content: Data ( ) )
783- }
784- )
839+ // Install npm dependencies used in the test harness, unless a shared node_modules is
840+ // provided in a parent directory (see planSharedNodeModules).
841+ if installNodeModules {
842+ allTasks. append (
843+ planNpmInstall (
844+ make: & make,
845+ intermediatesDirTask: intermediatesDirTask,
846+ packageJsonTask: packageJsonTask
847+ )
848+ )
849+ }
785850
786851 let binDir = outputDir. appending ( path: " bin " )
787852 let binDirTask = make. addTask (
0 commit comments