From 09a4e67e9efa218d6e07ee10c664949a0b039da7 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 1 Dec 2020 16:20:46 +0000 Subject: [PATCH] Serve main bundle resources from root directory (#176) This is required to make the `Image` view work as described in https://github.com/TokamakUI/Tokamak/pull/155#issuecomment-723677472. * Demangle and print Firefox stacktraces in terminal * Update the entrypoints archive URL, rename file * Add missing newline to `ProcessRunner.swift` * Remove redundant `console.log` call from `dev.js` * Detect destination env from `User-Agent` header * Silence linter in tests where it can't be avoided * Add support for basic testing in browsers * Add a browser message on finish, shut down server * Serve main bundle resources from root directory * Fix archive hashes, bump JSKit in TestApp to 0.9.0 * Add comments to clarify behavior * Fix typo in doc comment in `Package.swift` --- Sources/SwiftToolchain/Package.swift | 5 ++++ Sources/SwiftToolchain/Toolchain.swift | 25 ++++++++++--------- Sources/carton/Commands/Bundle.swift | 32 ++++++++++++++++++++++--- Sources/carton/Commands/Dev.swift | 3 ++- Sources/carton/Commands/Test.swift | 8 ++----- Sources/carton/Server/Application.swift | 15 ++++++++++++ Sources/carton/Server/Server.swift | 2 ++ TestApp/Package.resolved | 4 ++-- TestApp/Package.swift | 2 +- TestApp/Sources/TestApp/main.swift | 3 +++ 10 files changed, 73 insertions(+), 26 deletions(-) diff --git a/Sources/SwiftToolchain/Package.swift b/Sources/SwiftToolchain/Package.swift index 06949114..b7bc235b 100644 --- a/Sources/SwiftToolchain/Package.swift +++ b/Sources/SwiftToolchain/Package.swift @@ -66,6 +66,11 @@ struct ProductType: Codable { public struct Product: Codable { let name: String let type: ProductType + + /** List of names of targets that this product is composed of. Can be used to infer actual + target descriptions used in this product. + */ + public let targets: [String] } public enum TargetType: String, Codable { diff --git a/Sources/SwiftToolchain/Toolchain.swift b/Sources/SwiftToolchain/Toolchain.swift index 3b31e57a..d721954a 100644 --- a/Sources/SwiftToolchain/Toolchain.swift +++ b/Sources/SwiftToolchain/Toolchain.swift @@ -114,26 +114,25 @@ public final class Toolchain { return AbsolutePath(binPath) } - private func inferDevProduct(hint: String?) throws -> String? { + private func inferDevProduct(hint: String?) throws -> Product? { let package = try self.package.get() var candidateProducts = package.products .filter { $0.type.library == nil } - .map(\.name) - if let product = hint { - candidateProducts = candidateProducts.filter { $0 == product } + if let productName = hint { + candidateProducts = candidateProducts.filter { $0.name == productName } guard candidateProducts.count == 1 else { terminal.write(""" Failed to disambiguate the executable product, \ - make sure `\(product)` product is present in Package.swift + make sure `\(productName)` product is present in Package.swift """, inColor: .red) return nil } - terminal.logLookup("- development product: ", product) - return product + terminal.logLookup("- development product: ", productName) + return candidateProducts[0] } else if candidateProducts.count == 1 { return candidateProducts[0] } else { @@ -194,7 +193,7 @@ public final class Toolchain { public func buildCurrentProject( product: String?, isRelease: Bool - ) throws -> (builderArguments: [String], mainWasmPath: AbsolutePath) { + ) throws -> (builderArguments: [String], mainWasmPath: AbsolutePath, Product) { guard let product = try inferDevProduct(hint: product) else { throw ToolchainError.noExecutableProduct } @@ -217,14 +216,14 @@ public final class Toolchain { } let binPath = try inferBinPath(isRelease: isRelease) - let mainWasmPath = binPath.appending(component: "\(product).wasm") + let mainWasmPath = binPath.appending(component: "\(product.name).wasm") terminal.logLookup("- development binary to serve: ", mainWasmPath.pathString) terminal.write("\nBuilding the project before spinning up a server...\n", inColor: .yellow) let builderArguments = [ - swiftPath.pathString, "build", "-c", isRelease ? "release" : "debug", "--product", product, - "--enable-test-discovery", "--triple", "wasm32-unknown-wasi", + swiftPath.pathString, "build", "-c", isRelease ? "release" : "debug", + "--product", product.name, "--enable-test-discovery", "--triple", "wasm32-unknown-wasi", ] try Builder(arguments: builderArguments, mainWasmPath: mainWasmPath, fileSystem, terminal) @@ -235,10 +234,10 @@ public final class Toolchain { "Failed to build the main executable binary, fix the build errors and restart\n", inColor: .red ) - throw ToolchainError.failedToBuild(product: product) + throw ToolchainError.failedToBuild(product: product.name) } - return (builderArguments, mainWasmPath) + return (builderArguments, mainWasmPath, product) } /// Returns an absolute path to the resulting test bundle diff --git a/Sources/carton/Commands/Bundle.swift b/Sources/carton/Commands/Bundle.swift index 594183f4..d2b5a737 100644 --- a/Sources/carton/Commands/Bundle.swift +++ b/Sources/carton/Commands/Bundle.swift @@ -48,7 +48,7 @@ struct Bundle: ParsableCommand { let toolchain = try Toolchain(localFileSystem, terminal) - let (_, mainWasmPath) = try toolchain.buildCurrentProject( + let (_, mainWasmPath, inferredProduct) = try toolchain.buildCurrentProject( product: product, isRelease: !debug ) @@ -85,7 +85,8 @@ struct Bundle: ParsableCommand { optimizedPath: optimizedPath, buildDirectory: mainWasmPath.parentDirectory, bundleDirectory: bundleDirectory, - toolchain: toolchain + toolchain: toolchain, + product: inferredProduct ) terminal.write("Bundle generation finished successfully\n", inColor: .green, bold: true) @@ -102,7 +103,8 @@ struct Bundle: ParsableCommand { optimizedPath: AbsolutePath, buildDirectory: AbsolutePath, bundleDirectory: AbsolutePath, - toolchain: Toolchain + toolchain: Toolchain, + product: Product ) throws { // Rename the final binary to use a part of its hash to bust browsers and CDN caches. let optimizedHash = try localFileSystem.readFileContents(optimizedPath).hexSHA256.prefix(16) @@ -144,5 +146,29 @@ struct Bundle: ParsableCommand { terminal.logLookup("Copying resources to ", targetDirectory) try localFileSystem.copy(from: resourcesPath, to: targetDirectory) } + + /* While a product may be composed of multiple targets, not sure this is widely used in + practice. Just assuming here that the first target of this product is an executable target, + at least until SwiftPM allows specifying executable targets explicitly, as proposed in + swiftlint:disable:next line_length + https://forums.swift.org/t/pitch-ability-to-declare-executable-targets-in-swiftpm-manifests-to-support-main/41968 + */ + let inferredMainTarget = package.targets.first { + product.targets.contains($0.name) + } + + guard let mainTarget = inferredMainTarget else { return } + + let targetPath = package.resourcesPath(for: mainTarget) + let resourcesPath = buildDirectory.appending(component: targetPath) + for file in try localFileSystem.traverseRecursively(resourcesPath) { + let targetPath = bundleDirectory.appending(component: file.basename) + + guard localFileSystem.exists(resourcesPath) && !localFileSystem.exists(targetPath) + else { continue } + + terminal.logLookup("Copying this resource to the root bundle directory ", file) + try localFileSystem.copy(from: file, to: targetPath) + } } } diff --git a/Sources/carton/Commands/Dev.swift b/Sources/carton/Commands/Dev.swift index e471c1c2..90ed5311 100644 --- a/Sources/carton/Commands/Dev.swift +++ b/Sources/carton/Commands/Dev.swift @@ -78,7 +78,7 @@ struct Dev: ParsableCommand { ) } - let (arguments, mainWasmPath) = try toolchain.buildCurrentProject( + let (arguments, mainWasmPath, inferredProduct) = try toolchain.buildCurrentProject( product: product, isRelease: release ) @@ -110,6 +110,7 @@ struct Dev: ParsableCommand { customIndexContent: HTML.readCustomIndexPage(at: customIndexPage, on: localFileSystem), // swiftlint:disable:next force_try package: try! toolchain.package.get(), + product: inferredProduct, entrypoint: Self.entrypoint ), terminal diff --git a/Sources/carton/Commands/Test.swift b/Sources/carton/Commands/Test.swift index b3994a8d..a67c2cea 100644 --- a/Sources/carton/Commands/Test.swift +++ b/Sources/carton/Commands/Test.swift @@ -50,12 +50,7 @@ struct Test: ParsableCommand { @Argument(help: "The list of test cases to run in the test suite.") var testCases = [String]() - @Option( - help: """ - Environment used to run the tests, either a browser, or command-line Wasm host. - Possible values: `defaultBrowser` or `wasmer`. - """ - ) + @Option(help: "Environment used to run the tests, either a browser, or command-line Wasm host.") private var environment = Environment.wasmer @Option( @@ -94,6 +89,7 @@ struct Test: ParsableCommand { customIndexContent: nil, // swiftlint:disable:next force_try package: try! toolchain.package.get(), + product: nil, entrypoint: Self.entrypoint ), terminal diff --git a/Sources/carton/Server/Application.swift b/Sources/carton/Server/Application.swift index a35a5a39..bfa36949 100644 --- a/Sources/carton/Server/Application.swift +++ b/Sources/carton/Server/Application.swift @@ -23,6 +23,7 @@ extension Application { let mainWasmPath: AbsolutePath let customIndexContent: String? let package: SwiftToolchain.Package + let product: Product? let entrypoint: Entrypoint let onWebSocketOpen: (WebSocket, DestinationEnvironment) -> () let onWebSocketClose: (WebSocket) -> () @@ -71,5 +72,19 @@ extension Application { ).pathString)) } } + + let inferredMainTarget = configuration.package.targets.first { + configuration.product?.targets.contains($0.name) == true + } + + guard let mainTarget = inferredMainTarget else { return } + + let resourcesPath = configuration.package.resourcesPath(for: mainTarget) + get("**") { + $0.eventLoop.makeSucceededFuture($0.fileio.streamFile(at: AbsolutePath( + buildDirectory.appending(component: resourcesPath), + $0.parameters.getCatchall().joined(separator: "/") + ).pathString)) + } } } diff --git a/Sources/carton/Server/Server.swift b/Sources/carton/Server/Server.swift index b6d823de..dce4031e 100644 --- a/Sources/carton/Server/Server.swift +++ b/Sources/carton/Server/Server.swift @@ -83,6 +83,7 @@ final class Server { let port: Int let customIndexContent: String? let package: SwiftToolchain.Package + let product: Product? let entrypoint: Entrypoint } @@ -111,6 +112,7 @@ final class Server { mainWasmPath: configuration.mainWasmPath, customIndexContent: configuration.customIndexContent, package: configuration.package, + product: configuration.product, entrypoint: configuration.entrypoint, onWebSocketOpen: { [weak self] ws, environment in ws.onText { _, text in diff --git a/TestApp/Package.resolved b/TestApp/Package.resolved index ee38f711..ad2bfcf7 100644 --- a/TestApp/Package.resolved +++ b/TestApp/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swiftwasm/JavaScriptKit", "state": { "branch": null, - "revision": "8ba4135d5fd6a734c3771ef3fac66896bbcb0214", - "version": "0.8.0" + "revision": "b7a02434c6e973c08c3fd5069105ef44dd82b891", + "version": "0.9.0" } } ] diff --git a/TestApp/Package.swift b/TestApp/Package.swift index 85763469..64ded5b8 100644 --- a/TestApp/Package.swift +++ b/TestApp/Package.swift @@ -9,7 +9,7 @@ let package = Package( .executable(name: "TestApp", targets: ["TestApp"]), ], dependencies: [ - .package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.8.0"), + .package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.9.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test diff --git a/TestApp/Sources/TestApp/main.swift b/TestApp/Sources/TestApp/main.swift index f6981b66..f9f36605 100644 --- a/TestApp/Sources/TestApp/main.swift +++ b/TestApp/Sources/TestApp/main.swift @@ -45,6 +45,9 @@ buttonNode.onclick = .function(handler) var div = document.createElement("div") div.innerHTML = .string(#""" Link to a static resource +
+Link to a Bundle.main resource """#) _ = document.body.appendChild(div)