Skip to content

Commit ec72a95

Browse files
PackageToJS: run per-target test runners under the swiftbuild build system
`swift package js test` assumed the native build system's single combined `<Package>PackageTests` binary: it looked for one `.wasm`/`.xctest` under `.build/<config>/`, packaged it, and ran it. SwiftBuild produces no combined test binary. It emits one `<TestTarget>-test-runner.wasm` per test target under `.build/out/Products/`, plus an aggregate target that only orchestrates building them, so the old lookup failed with "Failed to find 'JavaScriptKitPackageTests.wasm'". (cherry picked from commit c12c7e9)
1 parent 0c6e582 commit ec72a95

2 files changed

Lines changed: 274 additions & 88 deletions

File tree

Plugins/PackageToJS/Sources/PackageToJS.swift

Lines changed: 103 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)