Skip to content

Commit

Permalink
Serve main bundle resources from root directory (#176)
Browse files Browse the repository at this point in the history
This is required to make the `Image` view work as described in TokamakUI/Tokamak#155 (comment).

* 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`
  • Loading branch information
MaxDesiatov authored Dec 1, 2020
1 parent ef402e7 commit 09a4e67
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 26 deletions.
5 changes: 5 additions & 0 deletions Sources/SwiftToolchain/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 12 additions & 13 deletions Sources/SwiftToolchain/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }

Expand All @@ -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)
Expand All @@ -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
Expand Down
32 changes: 29 additions & 3 deletions Sources/carton/Commands/Bundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
}
3 changes: 2 additions & 1 deletion Sources/carton/Commands/Dev.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct Dev: ParsableCommand {
)
}

let (arguments, mainWasmPath) = try toolchain.buildCurrentProject(
let (arguments, mainWasmPath, inferredProduct) = try toolchain.buildCurrentProject(
product: product,
isRelease: release
)
Expand Down Expand Up @@ -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
Expand Down
8 changes: 2 additions & 6 deletions Sources/carton/Commands/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions Sources/carton/Server/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) -> ()
Expand Down Expand Up @@ -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))
}
}
}
2 changes: 2 additions & 0 deletions Sources/carton/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ final class Server {
let port: Int
let customIndexContent: String?
let package: SwiftToolchain.Package
let product: Product?
let entrypoint: Entrypoint
}

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions TestApp/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion TestApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions TestApp/Sources/TestApp/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ buttonNode.onclick = .function(handler)
var div = document.createElement("div")
div.innerHTML = .string(#"""
<a href=\#(Bundle.module.path(forResource: "data", ofType: "json")!)>Link to a static resource</a>
<br/>
<a href=\#(Bundle.main
.path(forResource: "data", ofType: "json")!)>Link to a <code>Bundle.main</code> resource</a>
"""#)
_ = document.body.appendChild(div)

Expand Down

0 comments on commit 09a4e67

Please sign in to comment.