Skip to content

Version 1.0.0: SwiftPM Plugin Support #420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: Build the project
run: |
swift -v
swift build -Xswiftc -Xfrontend -Xswiftc -validate-tbd-against-ir=none
swift build

- name: Build and install JavaScript and sanitizer resources
run: |
Expand All @@ -60,7 +60,7 @@ jobs:
if [ -e /home/runner/.wasmer/wasmer.sh ]; then
source /home/runner/.wasmer/wasmer.sh
fi
swift test -Xswiftc -Xfrontend -Xswiftc -validate-tbd-against-ir=none
swift test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
18 changes: 0 additions & 18 deletions .github/workflows/swiftlint.yml

This file was deleted.

54 changes: 51 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
# 1.0.0 (XX XXX 2024)

**Breaking changes:**

`carton` CLI is now slimmed down to be a SwiftPM Plugin.
This means that you can now use `carton` by just declaring it as a dependency in your `Package.swift` file.

```swift
dependencies: [
.package(url: "https://github.com/swiftwasm/carton", from: "1.0.0"),
],
```

Each `carton` subcommand is now split into a separate SwiftPM plugin.

| Old command | New command |
| --------------- | ------------------------- |
| `carton dev` | `swift run carton dev` |
| `carton test` | `swift run carton test` |
| `carton bundle` | `swift run carton bundle` |

Also `carton` no longer supports the following features:

- `carton init` command (use `swift package init --type executable` instead)

**Internal changes:**

- Reduce build time by removing unnecessary dependencies: 96.97s -> 25.26s
- No longer directly depend on SwiftPM as a library. This means that `carton` no longer has to be updated when SwiftPM is updated (hopefully).

Our new SwiftPM plugin oriented architecture is illustrated in the following diagram:

```mermaid
sequenceDiagram
participant SwiftRunCarton as swift run carton
participant SwiftPM
participant CartonDevPlugin as carton-dev Plugin
participant CartonFrontend
SwiftRunCarton->>SwiftPM: exec
SwiftPM->>CartonDevPlugin: spawn
CartonDevPlugin->>SwiftPM: Build product
SwiftPM->>CartonDevPlugin: Build artifacts
CartonDevPlugin->>CartonFrontend: spawn
note right of CartonDevPlugin: Establish IPC
CartonFrontend->>CartonDevPlugin: File changed
CartonDevPlugin->>SwiftPM: Build product
SwiftPM->>CartonDevPlugin: Build artifacts
CartonDevPlugin->>CartonFrontend: Reload browsers
```

# 0.20.1 (25 Jan 2024)

This release fixes a bug in `carton test` where it reports missing `sock_accept` syscall.
Expand All @@ -12,10 +62,9 @@ This release adds SwiftWasm 5.9 toolchain support.
- Add Swift 5.9 to Build Action by @STREGA in https://github.com/swiftwasm/carton/pull/409
- Swift 5.9 toolchain & macOS Sonoma beta by @furby-tm in https://github.com/swiftwasm/carton/pull/402
- Add 5.9 support by @STREGA in https://github.com/swiftwasm/carton/pull/412
- Stop bothering WASI apps including unimplemented syscalls by @kateinoigakukun in https://github.com/swiftwasm/carton/pull/415
- Stop bothering WASI apps including unimplemented syscalls by @kateinoigakukun in https://github.com/swiftwasm/carton/pull/415
- Update default toolchain version to 5.9.1 by @kateinoigakukun in https://github.com/swiftwasm/carton/pull/416


# 0.19.1 (9 May 2023)

This release fixes the wrong toolchain version installed in docker image.
Expand All @@ -32,7 +81,6 @@ This release adds SwiftWasm 5.8 toolchain support.
- Support jammy and amazonlinux2 for toolchain install by @kateinoigakukun in https://github.com/swiftwasm/carton/pull/397
- Update default toolchain version to 5.8 channel snapshot by @kateinoigakukun in https://github.com/swiftwasm/carton/pull/398


# 0.18.0 (3 April 2023)

This release adds an extra size stripping optimization.
Expand Down
117 changes: 68 additions & 49 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,105 +5,126 @@ import PackageDescription

let package = Package(
name: "carton",
platforms: [.macOS("10.15.4")],
platforms: [.macOS(.v13)],
products: [
.library(name: "SwiftToolchain", targets: ["SwiftToolchain"]),
.library(name: "CartonHelpers", targets: ["CartonHelpers"]),
.library(name: "CartonKit", targets: ["CartonKit"]),
.library(name: "CartonCLI", targets: ["CartonCLI"]),
.executable(name: "carton", targets: ["Carton"]),
.executable(name: "carton", targets: ["carton"]),
.executable(name: "carton-release", targets: ["carton-release"]),
.plugin(name: "CartonBundle", targets: ["CartonBundle"]),
.plugin(name: "CartonTest", targets: ["CartonTest"]),
.plugin(name: "CartonDev", targets: ["CartonDev"]),
.executable(name: "carton-plugin-helper", targets: ["carton-plugin-helper"]),
],
dependencies: [
.package(
url: "https://github.com/swift-server/async-http-client.git",
from: "1.8.1"
),
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
.package(
url: "https://github.com/apple/swift-argument-parser.git",
.upToNextMinor(from: "1.2.3")
.upToNextMinor(from: "1.3.0")
),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.34.0"),
.package(
url: "https://github.com/apple/swift-package-manager.git",
branch: "release/5.9"
),
.package(
url: "https://github.com/apple/swift-tools-support-core.git",
branch: "release/5.9"
),
.package(url: "https://github.com/vapor/vapor.git", from: "4.57.1"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "2.2.0"),
.package(url: "https://github.com/JohnSundell/Splash.git", from: "0.16.0"),
.package(
url: "https://github.com/swiftwasm/WasmTransformer",
.upToNextMinor(from: "0.5.0")
),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module
// or a test suite. Targets can depend on other targets in this package, and on
// products in packages which this package depends on.
.executableTarget(
name: "Carton",
name: "carton",
dependencies: [
"SwiftToolchain",
"CartonHelpers",
]
),
.executableTarget(
name: "CartonFrontend",
dependencies: [
"CartonCLI",
]
),
.plugin(
name: "CartonBundle",
capability: .command(
intent: .custom(
verb: "carton-bundle",
description: "Produces an optimized app bundle for distribution."
)
),
dependencies: ["CartonFrontend"],
exclude: ["CartonPluginShared/README.md"]
),
.plugin(
name: "CartonTest",
capability: .command(
intent: .custom(
verb: "carton-test",
description: "Run the tests in a WASI environment."
)
),
dependencies: ["CartonFrontend"],
exclude: ["CartonPluginShared/README.md"]
),
.plugin(
name: "CartonDev",
capability: .command(
intent: .custom(
verb: "carton-dev",
description: "Watch the current directory, host the app, rebuild on change."
)
),
dependencies: ["CartonFrontend"],
exclude: ["CartonPluginShared/README.md"]
),
.executableTarget(name: "carton-plugin-helper"),
.target(
name: "CartonCLI",
dependencies: ["CartonKit"]
dependencies: [
.product(name: "Logging", package: "swift-log"),
"CartonKit",
]
),
.target(
name: "CartonKit",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "Vapor", package: "vapor"),
.product(name: "NIOWebSocket", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"CartonHelpers",
"SwiftToolchain",
"WebDriverClient",
]
"WasmTransformer",
],
exclude: ["Utilities/README.md"]
),
.target(
name: "SwiftToolchain",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
.product(name: "SwiftPMDataModel-auto", package: "swift-package-manager"),
"CartonHelpers",
"WasmTransformer",
]
],
exclude: ["Utilities/README.md"]
),
.target(
name: "CartonHelpers",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
"Splash",
"WasmTransformer",
]
dependencies: [],
exclude: ["Basics/README.md"]
),
.target(name: "WebDriverClient", dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
]),
.target(name: "WebDriverClient", dependencies: []),
// This target is used only for release automation tasks and
// should not be installed by `carton` users.
.executableTarget(
name: "carton-release",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
"CartonHelpers",
"WasmTransformer",
]
),
.testTarget(
name: "CartonTests",
dependencies: [
"Carton",
"CartonFrontend",
"CartonHelpers",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
Expand All @@ -113,8 +134,6 @@ let package = Package(
dependencies: [
"CartonCLI",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "TSCTestSupport", package: "swift-tools-support-core"),
]
),
.testTarget(name: "WebDriverClientTests", dependencies: ["WebDriverClient"]),
Expand Down
1 change: 1 addition & 0 deletions Plugins/CartonBundle/CartonPluginShared
89 changes: 89 additions & 0 deletions Plugins/CartonBundle/Plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 Carton contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import PackagePlugin

@main
struct CartonBundlePlugin: CommandPlugin {

struct Options {
var product: String?
var outputDir: String?
var debug: Bool

static func parse(from extractor: inout ArgumentExtractor) -> Options {
let product = extractor.extractOption(named: "product").last
let outputDir = extractor.extractOption(named: "output").last
let debug = extractor.extractFlag(named: "debug")
return Options(product: product, outputDir: outputDir, debug: debug != 0)
}
}

func performCommand(context: PluginContext, arguments: [String]) async throws {
try checkSwiftVersion()
try checkHelpFlag(arguments, subcommand: "bundle", context: context)

var extractor = ArgumentExtractor(arguments)
let options = Options.parse(from: &extractor)

let productName = try options.product ?? deriveDefaultProduct(package: context.package)

// Build products
let parameters = PackageManager.BuildParameters(
configuration: options.debug ? .debug : .release,
logging: .verbose
)
print("Building \"\(productName)\"")
let build = try self.packageManager.build(.product(productName), parameters: parameters)

guard build.succeeded else {
print(build.logText)
exit(1)
}

guard let product = try context.package.products(named: [productName]).first else {
throw CartonPluginError("Failed to find product named \"\(productName)\"")
}
guard let executableProduct = product as? ExecutableProduct else {
throw CartonPluginError(
"Product type of \"\(productName)\" is not supported. Only executable products are supported."
)
}

let productArtifact = try build.findWasmArtifact(for: productName)

let resourcesPaths = deriveResourcesPaths(
productArtifactPath: productArtifact.path,
sourceTargets: executableProduct.targets,
package: context.package
)

let bundleDirectory =
options.outputDir ?? context.pluginWorkDirectory.appending(subpath: "Bundle").string
let frontendArguments =
["bundle", productArtifact.path.string, "--output", bundleDirectory]
+ resourcesPaths.flatMap {
["--resources", $0.string]
} + extractor.remainingArguments
let frontend = try makeCartonFrontendProcess(context: context, arguments: frontendArguments)
frontend.forwardTerminationSignals()
try frontend.run()
frontend.waitUntilExit()
if frontend.terminationStatus == 0 {
print("Bundle written in \(bundleDirectory)")
}
frontend.checkNonZeroExit()
}
}
1 change: 1 addition & 0 deletions Plugins/CartonDev/CartonPluginShared
Loading