Update static.zip, automate its release process (#60)

`dev.js` entrypoint has been updated to include the recent JavaScriptKit runtime fix: https://github.com/swiftwasm/JavaScriptKit/pull/19

I've also added a new subcommand to `carton-release`, which is now able to automaticallly create a new `static.zip` and record updated hashes in the source code. It still doesn't upload the archive automatically to previous release assets, but I wonder if that should be done manually anyway, at least until we have some kind of tests that verify the whole process end-to-end.

Additionally, since the new runtime is not compatible with the old Swift parts of JavaScriptKit, `carton dev` now checks the revision of JavaScriptKit that projects have specified in their `Package.swift`. It doesn't block the build process, but I hope it gives enough warning about the incompatibility.
This commit is contained in:
Max Desiatov 2020-07-19 21:53:04 +01:00 committed by GitHub
parent 816984a674
commit 749a17d730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 251 additions and 101 deletions

4
.gitignore vendored
View File

@ -94,3 +94,7 @@ Public
# Node.js
node_modules
# carton
static
static.zip

View File

@ -66,6 +66,7 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
"CartonHelpers",
]
),
.testTarget(

View File

@ -60,6 +60,7 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
"CartonHelpers",
]
),
.testTarget(

View File

@ -23,6 +23,24 @@ struct Package: Codable {
let name: String
let products: [Product]
let targets: [Target]
let dependencies: [Dependency]?
struct Dependency: Codable {
let name: String
let requirement: Requirement
struct Requirement: Codable {
let range: [Range]?
let branch: [String]?
let revision: [String]?
let exact: [String]?
struct Range: Codable {
let lowerBound: String
let upperBound: String
}
}
}
init(with swiftPath: AbsolutePath, _ terminal: TerminalController) throws {
terminal.write("\nParsing package manifest: ", inColor: .yellow)

View File

@ -16,6 +16,8 @@ import CartonHelpers
import Foundation
import TSCBasic
private let compatibleJSKitRevision = "c90e82f"
enum ToolchainError: Error, CustomStringConvertible {
case directoryDoesNotExist(AbsolutePath)
case invalidResponseCode(UInt)
@ -148,6 +150,23 @@ public final class Toolchain {
guard let product = try inferDevProduct(hint: product)
else { throw ToolchainError.noExecutableProduct }
if let package = package,
let jsKit = package.dependencies?.first(where: { $0.name == "JavaScriptKit" }),
jsKit.requirement.revision != ["c90e82f"] {
let version = jsKit.requirement.revision.flatMap { " (\($0[0]))" } ?? ""
terminal.write(
"""
This revision of JavaScriptKit\(version) is not known to be compatible with \
carton \(cartonVersion). Please specify a JavaScriptKit dependency to revision \
\(compatibleJSKitRevision) in your `Package.swift`.\n
""",
inColor: .red
)
}
let binPath = try inferBinPath()
let mainWasmPath = binPath.appending(component: product)
terminal.logLookup("- development binary to serve: ", mainWasmPath.pathString)

View File

@ -0,0 +1,68 @@
// Copyright 2020 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 ArgumentParser
import AsyncHTTPClient
import TSCBasic
struct Formula: ParsableCommand {
@Argument() var version: String
func run() throws {
let archiveURL = "https://github.com/swiftwasm/carton/archive/\(version).tar.gz"
let client = HTTPClient(eventLoopGroupProvider: .createNew)
let response: HTTPClient.Response = try await {
client.get(url: archiveURL).whenComplete($0)
}
try client.syncShutdown()
guard
var body = response.body,
let bytes = body.readBytes(length: body.readableBytes)
else { fatalError("download failed for URL \(archiveURL)") }
let downloadedArchive = ByteString(bytes)
let sha256 = SHA256().hash(downloadedArchive).hexadecimalRepresentation
let formula = #"""
class Carton < Formula
desc "📦 Watcher, bundler, and test runner for your SwiftWasm apps"
homepage "https://carton.dev"
head "https://github.com/swiftwasm/carton.git"
depends_on :xcode => "11.4"
stable do
version "\#(version)"
url "https://github.com/swiftwasm/carton/archive/#{version}.tar.gz"
sha256 "\#(sha256)"
end
def install
system "swift", "build", "--disable-sandbox", "-c", "release"
system "mv", ".build/release/carton", "carton"
bin.install "carton"
end
test do
system "carton -h"
end
end
"""#
print(formula)
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2020 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 ArgumentParser
import CartonHelpers
import TSCBasic
struct HashArchive: ParsableCommand {
/** Converts a hexadecimal hash string to Swift code that represents a static
*/
private func arrayString(from hash: String) -> String {
precondition(hash.count == 64)
let commaSeparated = stride(from: 0, to: hash.count, by: 2)
.map { hash.dropLast(hash.count - $0 - 2).suffix(2) }
.map { "0x\($0)" }
.joined(separator: ", ")
precondition(commaSeparated.count == 190)
return """
\(commaSeparated.prefix(95))
\(commaSeparated.suffix(94))
"""
}
func run() throws {
let terminal = TerminalController(stream: stdoutStream)!
let cwd = localFileSystem.currentWorkingDirectory!
try ProcessRunner(["npm", "run", "build"], terminal).waitUntilFinished()
let devHash = try SHA256().hash(
localFileSystem.readFileContents(AbsolutePath(cwd, RelativePath("static/dev.js")))
).hexadecimalRepresentation.uppercased()
try ProcessRunner(["zip", "static.zip", "static/*"], terminal).waitUntilFinished()
let archiveHash = try SHA256().hash(
localFileSystem.readFileContents(AbsolutePath(
localFileSystem.currentWorkingDirectory!,
RelativePath("static.zip")
))
).hexadecimalRepresentation.uppercased()
let hashes = """
import TSCBasic
let devDependencySHA256 = ByteString([
\(arrayString(from: devHash))
])
let staticArchiveHash = ByteString([
\(arrayString(from: archiveHash)),
])
"""
try localFileSystem.writeFileContents(
AbsolutePath(cwd, RelativePath("Sources/carton/Server/StaticArchive.swift")),
bytes: ByteString(encodingAsUTF8: hashes)
)
}
}

View File

@ -13,58 +13,12 @@
// limitations under the License.
import ArgumentParser
import AsyncHTTPClient
import TSCBasic
struct CartonRelease: ParsableCommand {
@Argument() var version: String
func run() throws {
let archiveURL = "https://github.com/swiftwasm/carton/archive/\(version).tar.gz"
let client = HTTPClient(eventLoopGroupProvider: .createNew)
let response: HTTPClient.Response = try await {
client.get(url: archiveURL).whenComplete($0)
}
try client.syncShutdown()
guard
var body = response.body,
let bytes = body.readBytes(length: body.readableBytes)
else { fatalError("download failed for URL \(archiveURL)") }
let downloadedArchive = ByteString(bytes)
let sha256 = SHA256().hash(downloadedArchive).hexadecimalRepresentation
let formula = #"""
class Carton < Formula
desc "📦 Watcher, bundler, and test runner for your SwiftWasm apps"
homepage "https://carton.dev"
head "https://github.com/swiftwasm/carton.git"
depends_on :xcode => "11.4"
stable do
version "\#(version)"
url "https://github.com/swiftwasm/carton/archive/#{version}.tar.gz"
sha256 "\#(sha256)"
end
def install
system "swift", "build", "--disable-sandbox", "-c", "release"
system "mv", ".build/release/carton", "carton"
bin.install "carton"
end
test do
system "carton -h"
end
end
"""#
print(formula)
}
static let configuration = CommandConfiguration(
abstract: "Carton release automation utility",
subcommands: [Formula.self, HashArchive.self]
)
}
CartonRelease.main()

View File

@ -16,7 +16,7 @@ import ArgumentParser
import CartonHelpers
struct Carton: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "📦 Watcher, bundler, and test runner for your SwiftWasm apps.",
version: cartonVersion,
subcommands: [Init.self, Dev.self, SDK.self, Test.self]

View File

@ -24,10 +24,7 @@ import TSCBasic
private let dependency = Dependency(
fileName: "dev.js",
sha256: ByteString([
0xFA, 0x06, 0xE2, 0xA3, 0x2D, 0x45, 0xB9, 0xBB, 0x95, 0x9A, 0x89, 0x64, 0x3F, 0x6D, 0xAF, 0x1C,
0xF5, 0x49, 0xFC, 0x34, 0x59, 0xC5, 0xE0, 0xA6, 0x01, 0x59, 0xEB, 0x0C, 0xE6, 0xB2, 0x0B, 0x0C,
])
sha256: devDependencySHA256
)
struct Dev: ParsableCommand {
@ -40,7 +37,7 @@ struct Dev: ParsableCommand {
@Flag(help: "When specified, build in the release mode.")
var release = false
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Watch the current directory, host the app, rebuild on change."
)

View File

@ -18,7 +18,7 @@ import SwiftToolchain
import TSCBasic
struct Init: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Create a Swift package for a new SwiftWasm project.",
subcommands: [ListTemplates.self]
)
@ -49,9 +49,9 @@ struct Init: ParsableCommand {
terminal.write(" in ")
terminal.write("\(name)\n", inColor: .cyan)
guard let packagePath = self.name == nil ?
localFileSystem.currentWorkingDirectory :
AbsolutePath(name, relativeTo: currentDir)
guard let packagePath = self.name == nil ?
localFileSystem.currentWorkingDirectory :
AbsolutePath(name, relativeTo: currentDir)
else {
terminal.write("Path to project could be created.\n", inColor: .red)
return

View File

@ -17,7 +17,7 @@ import SwiftToolchain
import TSCBasic
struct Install: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Install new Swift toolchain/SDK."
)

View File

@ -16,7 +16,7 @@ import ArgumentParser
import TSCBasic
struct ListTemplates: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "List the available templates"
)

View File

@ -16,7 +16,7 @@ import ArgumentParser
import TSCBasic
struct Local: ParsableCommand {
static var configuration = CommandConfiguration(abstract: """
static let configuration = CommandConfiguration(abstract: """
Prints SDK version used for the current project or saves it \
in the `.swift-version` file if a version is passed as an argument.
""")

View File

@ -15,7 +15,7 @@
import ArgumentParser
struct SDK: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Manage installed Swift toolchains and SDKs.",
subcommands: [Install.self, Versions.self, Local.self]
)

View File

@ -23,7 +23,7 @@ import SwiftToolchain
import TSCBasic
struct Test: ParsableCommand {
static var configuration = CommandConfiguration(abstract: "Run the tests in a WASI environment.")
static let configuration = CommandConfiguration(abstract: "Run the tests in a WASI environment.")
@Flag(help: "When specified, build in the release mode.")
var release = false

View File

@ -17,7 +17,7 @@ import SwiftToolchain
import TSCBasic
struct Versions: ParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Lists all installed toolchains/SDKs"
)

View File

@ -17,12 +17,8 @@ import Foundation
import TSCBasic
import TSCUtility
private let archiveHash = ByteString([
0x1D, 0xCC, 0x1A, 0x8B, 0x89, 0x3C, 0xFD, 0xF6, 0x07, 0xF3, 0x9A, 0xBE, 0x22, 0xF1, 0xB7, 0x22,
0x5B, 0x7B, 0x41, 0x86, 0x66, 0xDF, 0x98, 0x52, 0x2C, 0x7B, 0xE5, 0x54, 0x73, 0xD2, 0x3E, 0x8A,
])
private let archiveURL = "https://github.com/swiftwasm/carton/releases/download/0.3.0/static.zip"
private let staticArchiveURL =
"https://github.com/swiftwasm/carton/releases/download/0.3.1/static.zip"
private let verifyHash = Equality<ByteString, String> {
"""
@ -54,7 +50,7 @@ struct Dependency {
try fileSystem.removeFileTree(cartonDir)
let client = HTTPClient(eventLoopGroupProvider: .createNew)
let request = try HTTPClient.Request.get(url: archiveURL)
let request = try HTTPClient.Request.get(url: staticArchiveURL)
let response: HTTPClient.Response = try await {
client.execute(request: request).whenComplete($0)
}
@ -63,14 +59,14 @@ struct Dependency {
guard
var body = response.body,
let bytes = body.readBytes(length: body.readableBytes)
else { throw DependencyError.downloadFailed(url: archiveURL) }
else { throw DependencyError.downloadFailed(url: staticArchiveURL) }
terminal.logLookup("Polyfills archive successfully downloaded from ", archiveURL)
terminal.logLookup("Polyfills archive successfully downloaded from ", staticArchiveURL)
let downloadedArchive = ByteString(bytes)
let downloadedHash = SHA256().hash(downloadedArchive)
try verifyHash(downloadedHash, archiveHash, context: archiveURL)
try verifyHash(downloadedHash, staticArchiveHash, context: staticArchiveURL)
let archiveFile = cartonDir.appending(component: "static.zip")
try fileSystem.createDirectory(cartonDir, recursive: true)

View File

@ -150,7 +150,11 @@ extension Templates {
fileSystem: fileSystem,
project: project,
dependencies: [
.init(name: "Tokamak", url: "https://github.com/swiftwasm/Tokamak", version: .branch("main")),
.init(
name: "Tokamak",
url: "https://github.com/swiftwasm/Tokamak",
version: .branch("main")
),
],
targetDepencencies: [
.init(name: "TokamakDOM", package: "Tokamak"),
@ -158,8 +162,8 @@ extension Templates {
terminal
)
try fileSystem.writeFileContents(project.path.appending(
components: "Sources",
project.name,
components: "Sources",
project.name,
"main.swift"
)) {
"""
@ -176,7 +180,9 @@ extension Templates {
"""
.write(to: $0)
}
try fileSystem.writeFileContents(project.path.appending(components: "Sources", project.name, "ContentView.swift")) {
try fileSystem.writeFileContents(
project.path.appending(components: "Sources", project.name, "ContentView.swift")
) {
"""
import TokamakDOM

View File

@ -0,0 +1,11 @@
import TSCBasic
let devDependencySHA256 = ByteString([
0xE5, 0x21, 0x17, 0xB3, 0xA7, 0xDF, 0xFF, 0x9D, 0x48, 0x4F, 0x60, 0x06, 0x74, 0x4F, 0xC1, 0x6D,
0x28, 0x34, 0x23, 0xB4, 0xFD, 0xF0, 0xF8, 0xDC, 0x2C, 0xA2, 0xE4, 0xCF, 0x23, 0x54, 0xB1, 0x81,
])
let staticArchiveHash = ByteString([
0x5F, 0xC2, 0xAF, 0x9C, 0x0C, 0x3E, 0x1F, 0x65, 0x09, 0xE4, 0x16, 0xA3, 0x1B, 0x21, 0x20, 0x23,
0xA7, 0x7B, 0xD1, 0x32, 0xAF, 0x9C, 0x87, 0x6F, 0xF3, 0x08, 0x87, 0xF5, 0x99, 0x68, 0xD0, 0x7A,
])

View File

@ -5,8 +5,8 @@
"package": "JavaScriptKit",
"repositoryURL": "https://github.com/kateinoigakukun/JavaScriptKit",
"state": {
"branch": "85b8617",
"revision": "85b8617eb107e161ede6b8ded5f1eb88b54648e0",
"branch": "c90e82f",
"revision": "c90e82fe1d576a2ccd1aae798380bf80be7885fb",
"version": null
}
}

View File

@ -9,11 +9,12 @@ let package = Package(
.executable(name: "TestApp", targets: ["TestApp"]),
],
dependencies: [
.package(url: "https://github.com/kateinoigakukun/JavaScriptKit", .revision("85b8617")),
.package(url: "https://github.com/kateinoigakukun/JavaScriptKit", .revision("c90e82f")),
],
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.
// 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.
.target(name: "TestApp", dependencies: ["JavaScriptKit", "TestLibrary", "CustomPathTarget"]),
.target(name: "TestLibrary"),
.target(name: "CustomPathTarget", path: "CustomPathTarget"),

BIN
entrypoint/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

30
package-lock.json generated
View File

@ -239,9 +239,9 @@
"dev": true
},
"ajv-keywords": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz",
"integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==",
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz",
"integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==",
"dev": true
},
"ansi-regex": {
@ -713,9 +713,9 @@
}
},
"chokidar": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz",
"integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==",
"dev": true,
"optional": true,
"requires": {
@ -1233,9 +1233,9 @@
}
},
"enhanced-resolve": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz",
"integrity": "sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
"integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
@ -2428,9 +2428,9 @@
}
},
"neo-async": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"nice-try": {
@ -3481,9 +3481,9 @@
"dev": true
},
"tar-stream": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
"integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
"integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
"dev": true,
"requires": {
"bl": "^4.0.1",

View File

@ -1,5 +1,5 @@
const path = require("path");
const outputPath = path.resolve(__dirname, "Public");
const outputPath = path.resolve(__dirname, "static");
module.exports = {
entry: "./entrypoint/dev.js",