diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index c766995d2..80ad9b805 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -8,6 +8,8 @@ struct PackageToJS { var packageName: String? /// Whether to explain the build plan (default: false) var explain: Bool = false + /// Whether to print verbose output + var verbose: Bool = false /// Whether to use CDN for dependency packages (default: false) var useCDN: Bool = false /// Whether to enable code coverage collection (default: false) @@ -49,8 +51,6 @@ struct PackageToJS { var inspect: Bool /// The extra arguments to pass to node var extraNodeArguments: [String] - /// Whether to print verbose output - var verbose: Bool /// The options for packaging var packageOptions: PackageOptions } @@ -99,7 +99,7 @@ struct PackageToJS { try PackageToJS.runSingleTestingLibrary( testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, extraArguments: extraArguments, - testParser: testOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }), + testParser: testOptions.packageOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }), testOptions: testOptions ) } diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 2844d52ec..62e7dc16e 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -5,6 +5,7 @@ @preconcurrency import class Foundation.Process @preconcurrency import class Foundation.ProcessInfo @preconcurrency import class Foundation.FileManager +@preconcurrency import struct Foundation.CocoaError @preconcurrency import func Foundation.fputs @preconcurrency import func Foundation.exit @preconcurrency import var Foundation.stderr @@ -19,18 +20,18 @@ struct PackageToJSPlugin: CommandPlugin { // In case user misses the `--swift-sdk` option { build, arguments in guard - build.logText.contains( - "ld.gold: --export-if-defined=__main_argc_argv: unknown option") + build.logText.contains("ld.gold: --export-if-defined=__main_argc_argv: unknown option") || + build.logText.contains("-static-stdlib is no longer supported for Apple platforms") else { return nil } let didYouMean = [ "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", "js", ] + arguments return """ - Please pass the `--swift-sdk` option to the "swift package" command. + Please pass `--swift-sdk` to "swift package". - Did you mean: - \(didYouMean.joined(separator: " ")) + Did you mean this? + \(didYouMean.joined(separator: " ")) """ }), ( @@ -53,23 +54,84 @@ struct PackageToJSPlugin: CommandPlugin { 3. Select a matching SDK version with --swift-sdk option """ }), + ( + // In case selected toolchain is a Xcode toolchain, not OSS toolchain + { build, arguments in + guard build.logText.contains("No available targets are compatible with triple \"wasm32-unknown-wasi\"") else { + return nil + } + return """ + The selected toolchain might be an Xcode toolchain, which doesn't support WebAssembly target. + + Please use a swift.org Open Source toolchain with WebAssembly support. + See https://book.swiftwasm.org/getting-started/setup.html for more information. + """ + }), ] + + private func emitHintMessage(_ message: String) { + printStderr("\n" + "\u{001B}[1m\u{001B}[97mHint:\u{001B}[0m " + message) + } + private func reportBuildFailure( _ build: PackageManager.BuildResult, _ arguments: [String] ) { for diagnostic in Self.friendlyBuildDiagnostics { if let message = diagnostic(build, arguments) { - printStderr("\n" + message) + emitHintMessage(message) + return } } } func performCommand(context: PluginContext, arguments: [String]) throws { - if arguments.first == "test" { - return try performTestCommand(context: context, arguments: Array(arguments.dropFirst())) - } + do { + if arguments.first == "test" { + return try performTestCommand(context: context, arguments: Array(arguments.dropFirst())) + } + + return try performBuildCommand(context: context, arguments: arguments) + } catch let error as CocoaError where error.code == .fileWriteNoPermission { + guard let filePath = error.filePath else { throw error } - return try performBuildCommand(context: context, arguments: arguments) + let packageDir = context.package.directoryURL + printStderr("\n\u{001B}[1m\u{001B}[91merror:\u{001B}[0m \(error.localizedDescription)") + + if filePath.hasPrefix(packageDir.path) { + // Emit hint for --allow-writing-to-package-directory if the destination path + // is under the package directory + let didYouMean = [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-package-directory", + "js", + ] + arguments + emitHintMessage( + """ + Please pass `--allow-writing-to-package-directory` to "swift package". + + Did you mean this? + \(didYouMean.joined(separator: " ")) + """ + ) + } else { + // Emit hint for --allow-writing-to-directory + // if the destination path is outside the package directory + let didYouMean = [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-directory", "\(filePath)", + "js", + ] + arguments + emitHintMessage( + """ + Please pass `--allow-writing-to-directory ` to "swift package". + + Did you mean this? + \(didYouMean.joined(separator: " ")) + """ + ) + } + exit(1) + } } static let JAVASCRIPTKIT_PACKAGE_ID: Package.ID = "javascriptkit" @@ -94,7 +156,7 @@ struct PackageToJSPlugin: CommandPlugin { let productName = try buildOptions.product ?? deriveDefaultProduct(package: context.package) let build = try buildWasm( productName: productName, context: context, - enableCodeCoverage: buildOptions.packageOptions.enableCodeCoverage + options: buildOptions.packageOptions ) guard build.succeeded else { reportBuildFailure(build, arguments) @@ -150,7 +212,7 @@ struct PackageToJSPlugin: CommandPlugin { let productName = "\(context.package.displayName)PackageTests" let build = try buildWasm( productName: productName, context: context, - enableCodeCoverage: testOptions.packageOptions.enableCodeCoverage + options: testOptions.packageOptions ) guard build.succeeded else { reportBuildFailure(build, arguments) @@ -222,14 +284,15 @@ struct PackageToJSPlugin: CommandPlugin { } } - private func buildWasm(productName: String, context: PluginContext, enableCodeCoverage: Bool) throws + private func buildWasm(productName: String, context: PluginContext, options: PackageToJS.PackageOptions) throws -> PackageManager.BuildResult { var parameters = PackageManager.BuildParameters( configuration: .inherit, - logging: .concise + logging: options.verbose ? .verbose : .concise ) parameters.echoLogs = true + parameters.otherSwiftcFlags = ["-color-diagnostics"] let buildingForEmbedded = ProcessInfo.processInfo.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap( Bool.init) ?? false @@ -237,15 +300,15 @@ struct PackageToJSPlugin: CommandPlugin { // NOTE: We only support static linking for now, and the new SwiftDriver // does not infer `-static-stdlib` for WebAssembly targets intentionally // for future dynamic linking support. - parameters.otherSwiftcFlags = [ + parameters.otherSwiftcFlags += [ "-static-stdlib", "-Xclang-linker", "-mexec-model=reactor", ] - parameters.otherLinkerFlags = [ + parameters.otherLinkerFlags += [ "--export-if-defined=__main_argc_argv" ] // Enable code coverage options if requested - if enableCodeCoverage { + if options.enableCodeCoverage { parameters.otherSwiftcFlags += ["-profile-coverage-mapping", "-profile-generate"] parameters.otherCFlags += ["-fprofile-instr-generate", "-fcoverage-mapping"] } @@ -293,9 +356,10 @@ extension PackageToJS.PackageOptions { let packageName = extractor.extractOption(named: "package-name").last let explain = extractor.extractFlag(named: "explain") let useCDN = extractor.extractFlag(named: "use-cdn") + let verbose = extractor.extractFlag(named: "verbose") let enableCodeCoverage = extractor.extractFlag(named: "enable-code-coverage") return PackageToJS.PackageOptions( - outputPath: outputPath, packageName: packageName, explain: explain != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 + outputPath: outputPath, packageName: packageName, explain: explain != 0, verbose: verbose != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 ) } } @@ -320,30 +384,31 @@ extension PackageToJS.BuildOptions { return """ OVERVIEW: Builds a JavaScript module from a Swift package. - USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS [options] [subcommand] + USAGE: swift package --swift-sdk [SwiftPM options] js [options] [subcommand] OPTIONS: --product Product to build (default: executable target if there's only one) --output Path to the output directory (default: .build/plugins/PackageToJS/outputs/Package) --package-name Name of the package (default: lowercased Package.swift name) - --explain Whether to explain the build plan (default: false) - --no-optimize Whether to disable wasm-opt optimization (default: false) - --use-cdn Whether to use CDN for dependency packages (default: false) - --enable-code-coverage Whether to enable code coverage collection (default: false) + --explain Whether to explain the build plan + --verbose Whether to print verbose output + --no-optimize Whether to disable wasm-opt optimization + --use-cdn Whether to use CDN for dependency packages + --enable-code-coverage Whether to enable code coverage collection --debug-info-format The format of debug info to keep in the final wasm file (values: none, dwarf, name; default: none) SUBCOMMANDS: test Builds and runs tests EXAMPLES: - $ swift package --swift-sdk wasm32-unknown-wasi plugin js + $ swift package --swift-sdk wasm32-unknown-wasi js # Build a specific product - $ swift package --swift-sdk wasm32-unknown-wasi plugin js --product Example + $ swift package --swift-sdk wasm32-unknown-wasi js --product Example # Build in release configuration $ swift package --swift-sdk wasm32-unknown-wasi -c release plugin js # Run tests - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test + $ swift package --swift-sdk wasm32-unknown-wasi js test """ } } @@ -356,14 +421,12 @@ extension PackageToJS.TestOptions { let prelude = extractor.extractOption(named: "prelude").last let environment = extractor.extractOption(named: "environment").last let inspect = extractor.extractFlag(named: "inspect") - let verbose = extractor.extractFlag(named: "verbose") let extraNodeArguments = extractor.extractSingleDashOption(named: "Xnode") let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor) var options = PackageToJS.TestOptions( buildOnly: buildOnly != 0, listTests: listTests != 0, filter: filter, prelude: prelude, environment: environment, inspect: inspect != 0, extraNodeArguments: extraNodeArguments, - verbose: verbose != 0, packageOptions: packageOptions ) @@ -378,23 +441,24 @@ extension PackageToJS.TestOptions { return """ OVERVIEW: Builds and runs tests - USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS test [options] + USAGE: swift package --swift-sdk [SwiftPM options] js test [options] OPTIONS: - --build-only Whether to build only (default: false) + --build-only Whether to build only --prelude Path to the prelude script - --environment The environment to use for the tests + --environment The environment to use for the tests (values: node, browser; default: node) --inspect Whether to run tests in the browser with inspector enabled - --use-cdn Whether to use CDN for dependency packages (default: false) - --enable-code-coverage Whether to enable code coverage collection (default: false) - --verbose Whether to print verbose output (default: false) + --explain Whether to explain the build plan + --verbose Whether to print verbose output + --use-cdn Whether to use CDN for dependency packages + --enable-code-coverage Whether to enable code coverage collection -Xnode Extra arguments to pass to Node.js EXAMPLES: - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test --environment browser + $ swift package --swift-sdk wasm32-unknown-wasi js test + $ swift package --swift-sdk wasm32-unknown-wasi js test --environment browser # Just build tests, don't run them - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test --build-only + $ swift package --swift-sdk wasm32-unknown-wasi js test --build-only $ node .build/plugins/PackageToJS/outputs/PackageTests/bin/test.js """ } diff --git a/README.md b/README.md index c03561587..2f41b7a31 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # JavaScriptKit [![Run unit tests](https://github.com/swiftwasm/JavaScriptKit/actions/workflows/test.yml/badge.svg)](https://github.com/swiftwasm/JavaScriptKit/actions/workflows/test.yml) +[![](https://img.shields.io/badge/docc-read_documentation-blue)](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/documentation) Swift framework to interact with JavaScript through WebAssembly. ## Quick Start -Check out the [Hello World](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/main/tutorials/javascriptkit/hello-world) tutorial for a step-by-step guide to getting started. +Check out the [Hello World](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/tutorials/javascriptkit/hello-world) tutorial for a step-by-step guide to getting started. ## Overview diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 261b5b5cb..828cb40f2 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -21,7 +21,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { // 2. Create a new JavaScript function which calls the given Swift function. hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) - id = withExtendedLifetime(JSString(file)) { file in + _id = withExtendedLifetime(JSString(file)) { file in swjs_create_function(hostFuncRef, line, file.asInternalJSRef()) } @@ -105,7 +105,7 @@ public class JSClosure: JSFunction, JSClosureProtocol { // 2. Create a new JavaScript function which calls the given Swift function. hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) - id = withExtendedLifetime(JSString(file)) { file in + _id = withExtendedLifetime(JSString(file)) { file in swjs_create_function(hostFuncRef, line, file.asInternalJSRef()) } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 0958b33f4..9006ec7b7 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -19,16 +19,20 @@ public class JSObject: Equatable { internal static var constructor: JSFunction { _constructor.wrappedValue } private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Object.function! }) - @_spi(JSObject_id) - public var id: JavaScriptObjectRef + @usableFromInline + internal var _id: JavaScriptObjectRef #if compiler(>=6.1) && _runtime(_multithreaded) package let ownerTid: Int32 #endif + @_spi(JSObject_id) + @inlinable + public var id: JavaScriptObjectRef { _id } + @_spi(JSObject_id) public init(id: JavaScriptObjectRef) { - self.id = id + self._id = id #if compiler(>=6.1) && _runtime(_multithreaded) self.ownerTid = swjs_get_worker_thread_id_cached() #endif